Env setup

# install.packages(renv)
renv::init()
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
This project already has a lockfile. What would you like to do? 

1: Restore the project from the lockfile.
2: Discard the lockfile and re-initialize the project.
3: Activate the project without snapshotting or installing any packages.
4: Abort project initialization.
1
* Restoring project ... 
* The library is already synchronized with the lockfile.
renv::install("reticulate")
Retrieving 'https://packagemanager.rstudio.com/all/__linux__/focal/latest/src/contrib/reticulate_1.22.tar.gz' ...
    OK [downloaded 2.3 Mb in 1.1 secs]
Retrieving 'https://packagemanager.rstudio.com/all/__linux__/focal/latest/src/contrib/here_1.0.1.tar.gz' ...
    OK [downloaded 50.9 Kb in 1.4 secs]
Installing here [1.0.1] ...
    OK [installed binary]
Installing reticulate [1.22] ...
    OK [installed binary]
The following package(s) have been updated:

    reticulate [installed version 1.22 != loaded version 1.18]

Consider restarting the R session and loading the newly-installed packages.
renv::use_python()
* Activated Python 3.8.8 [virtualenv; /nfs/team205/ed6/bin/Pan_fetal_immune/src/5_organ_signatures/MOFA_factor_analysis/renv/python/virtualenvs/renv-python-3.8.8]
* Project '/nfs/team205/ed6/bin/Pan_fetal_immune/src/5_organ_signatures/MOFA_factor_analysis' loaded. [renv 0.12.5]
* The project may be out of sync -- use `renv::status()` for more details.
py_pkgs <- c(
    "scanpy",
    "anndata",
    "mofapy2"
)

reticulate::py_install(py_pkgs)
Error in (function (srcref)  : unimplemented type (29) in 'eval'
# setwd('~/Pan_fetal_immune/src/5_organ_signatures/MOFA_factor_analysis/')
# renv::activate()
# renv::use_python()
# 
# py_pkgs <- c(
#     "scanpy",
#     "anndata",
#     "mofapy2"
# )
# 
# reticulate::py_install(py_pkgs)
suppressPackageStartupMessages({
  library(tidyverse)
  library(MOFA2)
  library(Matrix)
  library(SingleCellExperiment)
  library(scran)
  library(glue)
  library(scater)
  library(patchwork)
  library(batchelor)
  library(rhdf5)
  # library(ggraph)
  }
  )
Error: package or namespace load failed for ‘tidyverse’ in loadNamespace(i, c(lib.loc, .libPaths()), versionCheck = vI[[i]]):
 namespace ‘ellipsis’ 0.3.1 is already loaded, but >= 0.3.2 is required

Define plotting utils

remove_x_axis <- function(){
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank())  
}

remove_y_axis <- function(){
  theme(axis.text.y = element_blank(), axis.ticks.y = element_blank(), axis.title.y = element_blank())  
}

org_colors <- read_csv("~/Pan_fetal_immune/metadata/organ_colors.csv")
Duplicated column names deduplicated: 'organ' => 'organ_1' [2]
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  organ = col_character(),
  organ_1 = col_character(),
  color = col_character()
)
org_colors <- setNames(org_colors$color, org_colors$organ)
figdir <- "~/mount/gdrive/Pan_fetal/Updates_and_presentations/figures/MOFA_analysis/"
if (!dir.exists(figdir)){ dir.create(figdir) }

Load pseudobulked data

split = "LYMPHOID"
indir <- glue("/nfs/team205/ed6/data/Fetal_immune/LMM_data/LMM_input_{split}_PBULK/")

matrix <- readMM(file = paste0(indir, "matrix.mtx.gz"))
coldata <- read.csv(file = paste0(indir, "metadata.csv.gz"))  %>%
  column_to_rownames("X")
rowdata <- read.csv(file = paste0(indir, "gene.csv.gz")) 

## Make SingleCellExperiment obj
sce <- SingleCellExperiment(list(logcounts = t(matrix)), colData = coldata)
rownames(sce) <- make.unique(rowdata$GeneName) 
## Plot number of cells per organ/celltype pair
n_cells_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_cells=sum(n_cells)) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=log10(n_cells))) +
  geom_text(aes(label=n_cells), color="white") +
  scale_fill_viridis_c() +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank())
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_samples_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_samples=n()) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=n_samples)) +
  geom_text(aes(label=n_samples), color="white") +
  scale_fill_viridis_c(option="cividis") +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5))
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_cells_heatmap / n_samples_heatmap

Preprocessing

Filtering samples

## Filter out samples with less than 20 cells
sce <- sce[,sce$n_cells > 20]

# Exclude celltypes present in just one organ
keep_ct <- data.frame(colData(sce)) %>%
  dplyr::select(organ, anno_lvl_2_final_clean) %>%
  distinct() %>%
  group_by(anno_lvl_2_final_clean) %>%
  summarise(n=n()) %>%
  ungroup() %>%
  filter(n > 1) %>%
  pull(anno_lvl_2_final_clean)

sce <- sce[,sce$anno_lvl_2_final_clean %in% keep_ct]

# Filter out celltypes with less than 10 samples
keep_ct <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean) %>%
  summarise(n_samples=n()) %>%
  filter(n_samples >= 10) %>%
  pull(anno_lvl_2_final_clean)

sce <- sce[,sce$anno_lvl_2_final_clean %in% keep_ct]

## Exclude low quality clusters
anno_groups <- jsonlite::fromJSON(txt =  "~/Pan_fetal_immune/metadata/anno_groups.json")
sce <- sce[,!sce$anno_lvl_2_final_clean %in% anno_groups$OTHER]

## Exclude donor F19 (low Q)
sce <- sce[,!sce$donor %in% c('F19')]
## Exclude donors for which we have only one organ
data.frame(colData(sce))[c("Sample", "donor", "organ", 'age')] %>%
  distinct(donor, organ, Sample, age) %>%
  group_by(donor) %>%
  mutate(n_organs = length(unique(organ))) %>%
  ungroup() %>%
  filter(n_organs >= 3) %>%
  pull(donor) %>%
  unique()
 [1] "F45" "F23" "F30" "F38" "F41" "F29" "F35" "F51" "F21" "F32" "F50"
sce <- sce[,sce$donor %in% keep.donors]
## Plot number of cells per organ/celltype pair
n_cells_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_cells=sum(n_cells)) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=log10(n_cells))) +
  geom_text(aes(label=n_cells), color="white") +
  scale_fill_viridis_c() +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank())
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_samples_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_samples=n()) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=n_samples)) +
  geom_text(aes(label=n_samples), color="white") +
  scale_fill_viridis_c(option="cividis") +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5))
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_cells_heatmap / n_samples_heatmap

organ_order <- c("YS", "LI", "BM", "TH", "SP", "SK","KI","GU", "MLN")
pl <- data.frame(colData(sce)) %>%
  dplyr::group_by(anno_lvl_2_final_clean, organ) %>%
  dplyr::summarise(n_cells=sum(n_cells), n_samples=dplyr::n()) %>%
  dplyr::group_by(anno_lvl_2_final_clean) %>%
  dplyr::mutate(n_organs=dplyr::n(), org_frac=n_cells/sum(n_cells)) %>%
  ## is the ct overrepresented in one organ?
  dplyr::mutate(delta_max_org_frac = max(org_frac)-org_frac) %>% 
  dplyr::mutate(mean_delta_max_org_frac = mean(delta_max_org_frac)) %>% 
  dplyr::ungroup() %>%
  dplyr::arrange(n_organs, -mean_delta_max_org_frac) %>%
  dplyr::mutate(anno_lvl_2_final_clean=factor(anno_lvl_2_final_clean, levels=rev(unique(anno_lvl_2_final_clean)))) %>%
  dplyr::mutate(organ=factor(organ, levels=rev(organ_order))) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_point(aes(color=log10(n_cells), size=n_samples)) +
  geom_text(aes(label=n_cells), color="white") +
  scale_size(range=c(7,18), breaks = c(0,1,10,30), name="# samples") +
  scale_fill_viridis_c() +
  scale_color_viridis_c(name="log10(# cells)") +
  theme_classic(base_size = 20) +
  xlab("celltype") +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5)) 
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
## Save order of CTs from widespread to restricted
anno_order <- levels(pl$data$anno_lvl_2_final_clean)

ggsave(paste0(figdir, "ct_organ_distribution.pdf"), pl, width = 15, height = 10)

Technical effect correction

## Feature selection w scran WITHIN CELLTYPE
anno_groups <- split(colnames(sce), sce$anno_lvl_2_final_clean)
all_hvgs <- c()
for (i in anno_groups){
  dec <- modelGeneVar(sce[,i])
  hvgs <- getTopHVGs(dec, n = 1000)
  all_hvgs <- union(all_hvgs, hvgs)
  }

sce <- sce[which(rowSums(logcounts(sce)) > 0),]
sce
class: SingleCellExperiment 
dim: 27615 757 
metadata(0):
assays(1): logcounts
rownames(27615): TSPAN6 TNMD ... AL356417.3 AP000646.1
rowData names(0):
colnames(757): F45_SK_CD45P_FCAImmP7579224-F45-SK-CD4+T-12-5GEX F45_SK_CD45P_FCAImmP7579224-F45-SK-CD8+T-12-5GEX ...
  F50_SP_CD45P_FCAImmP7803020-F50-SP-IMMATURE_B-15-5GEX F30_TH_CD45N_FCAImmP7277565-F30-TH-ABT(ENTRY)-14-3GEX
colData names(7): Sample donor ... method n_cells
reducedDimNames(0):
altExpNames(0):

EDA with PCA

sce <- runPCA(sce, scale=TRUE, ncomponents=30, 
              exprs_values = "logcounts", subset_row=all_hvgs)
plotPCA(sce, colour_by="donor", ncomponents=6)

plotPCA(sce, colour_by="method", ncomponents=6)

plotPCA(sce, colour_by="organ", ncomponents=10)

Minimize obvious technical effects (3GEX/5GEX, donor) using linear regression (following procedure from OSCA)

## Regress technical effects
design <- model.matrix(~donor+method,data=colData(sce))
residuals <- regressBatches(sce, assay.type = "logcounts", design = design)
assay(sce, "corrected_logcounts") <- as.matrix(assay(residuals[,colnames(sce)], "corrected"))

## Regress organ (soup effect)
design <- model.matrix(~organ,data=colData(sce)) ## Include organ term to capture soup
residuals <- regressBatches(sce, assay.type = "corrected_logcounts", design = design)
assay(sce, "corrected_logcounts") <- as.matrix(assay(residuals[,colnames(sce)], "corrected"))

Check regression has an effect repeating PCA

sce <- runPCA(sce, scale=TRUE, ncomponents=30, exprs_values = "corrected_logcounts")

plotPCA(sce, colour_by="method", ncomponents=6)

plotPCA(sce, colour_by="donor", ncomponents=6)

plotPCA(sce, colour_by="organ", ncomponents=8)

Feature selection

## Feature selection w scran WITHIN CELLTYPE
anno_groups <- split(colnames(sce), sce$anno_lvl_2_final_clean)
all_hvgs <- c()
for (i in anno_groups){
  dec <- modelGeneVar(sce[,i], assay.type = "corrected_logcounts")
  hvgs <- getTopHVGs(dec, n = 1000)
  all_hvgs <- union(all_hvgs, hvgs)
  }

FA Model - Normal MOFA / only celltypes as groups

Make MOFA object (Use celltypes as grouping covariate)

mofa_obj <- readRDS(glue('{indir}LYMPHOID_mofa_obj_organCorrected_filteredDonors.RDS'))
Error in glue("{indir}LYMPHOID_mofa_obj_organCorrected_filteredDonors.RDS") : 
  could not find function "glue"
object <- mofa_obj

Prepare 4 training


data_opts <- get_default_data_options(object)
data_opts$use_float32 <- TRUE
data_opts$center_groups <- FALSE
object@data_options <- data_opts

model_opts <- get_default_model_options(object)
model_opts$num_factors <- 30
# model_opts$ard_factors <- FALSE

train_opts <- get_default_training_options(object)
train_opts$seed <- 2020
train_opts$convergence_mode <- "medium" # use "fast" for faster training
train_opts$stochastic <- FALSE

# mefisto_opts <- get_default_mefisto_options(object)
# mefisto_opts$warping <- FALSE
# mefisto_opts$sparseGP <- TRUE

object <- prepare_mofa(
  object = object,
  data_options = data_opts,
  model_options = model_opts,
  training_options = train_opts
) 
Some group(s) have less than 10 samples, MOFA will have little power to learn meaningful factors for these group(s)...
# Multi-group mode requested.

This is an advanced option, if this is the first time that you are running MOFA, we suggest that you try do some exploration first without specifying groups. Two important remarks:

 - The aim of the multi-group framework is to identify the sources of variability *within* the groups. If your aim is to find a factor that 'separates' the groups, you DO NOT want to use the multi-group framework. Please see the FAQ on the MOFA2 webpage.

 - It is important to account for the group effect before selecting highly variable features (HVFs). We suggest that either you calculate HVFs per group and then take the union, or regress out the group effect before HVF selection
Checking data options...
Due to string size limitations in the HDF5 format, sample names will be trimmed to less than 50 charactersChecking training options...
Checking model options...
object
Untrained MOFA model with the following characteristics: 
 Number of views: 1 
 Views names: corrected_logcounts 
 Number of features (per view): 7481 
 Number of groups: 23 
 Groups names: ABT(ENTRY) B1 CD4+T CD8+T CD8AA CYCLING_MPP CYCLING_NK CYCLING_T HSC_MPP ILC3 IMMATURE_B LARGE_PRE_B LATE_PRO_B LMPP_ELP MATURE_B MEMP NK NK_T PRE_PRO_B PRO_B SMALL_PRE_B TH17 TREG 
 Number of samples (per group): 18 29 47 41 16 15 42 28 22 35 26 48 29 4 47 19 62 38 34 44 42 34 37 
 

Train

Wrapped in run_mofa.R

outfile <- glue('{indir}{split}_mofa_model_oneview_organCorrected_filteredDonors.hdf5')
mofa_trained <- run_mofa(object, outfile = outfile)
Connecting to the mofapy2 python package using reticulate (use_basilisk = FALSE)... 
    Please make sure to manually specify the right python binary when loading R with reticulate::use_python(..., force=TRUE) or the right conda environment with reticulate::use_condaenv(..., force=TRUE)
    If you prefer to let us automatically install a conda environment with 'mofapy2' installed using the 'basilisk' package, please use the argument 'use_basilisk = TRUE'

mofapy2_0.6.4 is not detected in the specified python binary, see reticulate::py_config(). Setting use_basilisk = TRUE...Connecting to the mofapy2 package using basilisk. 
    Set 'use_basilisk' to FALSE if you prefer to manually set the python binary using 'reticulate'.
/bin/bash: /opt/conda/lib/libtinfo.so.6: no version information available (required by /bin/bash)
Collecting package metadata (current_repodata.json): ...working... done
Solving environment: ...working... failed with repodata from current_repodata.json, will retry with next repodata source.
Collecting package metadata (repodata.json): ...working... failed

CondaError: KeyboardInterrupt

Error: Error 2 occurred creating conda environment /home/jovyan/.cache/basilisk/1.2.1/MOFA2-1.3.4/mofa_env
### Tweaking the MOFA2 loading function because the quality control complains
load_model <- function(file, sort_factors = TRUE, on_disk = FALSE, load_data = TRUE,
                       remove_outliers = FALSE, remove_inactive_factors = TRUE, verbose = FALSE,
                       load_interpol_Z = FALSE) {

  # Create new MOFAodel object
  object <- new("MOFA")
  object@status <- "trained"
  
  # Set on_disk option
  if (on_disk) { 
    object@on_disk <- TRUE 
  } else { 
      object@on_disk <- FALSE 
  }
  
  # Get groups and data set names from the hdf5 file object
  h5ls.out <- h5ls(file, datasetinfo = FALSE)
  
  ########################
  ## Load training data ##
  ########################

  # Load names
  if ("views" %in% h5ls.out$name) {
    view_names <- as.character( h5read(file, "views")[[1]] )
    group_names <- as.character( h5read(file, "groups")[[1]] )
    feature_names <- h5read(file, "features")[view_names]
    sample_names  <- h5read(file, "samples")[group_names] 
  } else {  # for old models
    feature_names <- h5read(file, "features")
    sample_names  <- h5read(file, "samples")
    view_names <- names(feature_names)
    group_names <- names(sample_names)
    h5ls.out <- h5ls.out[grep("variance_explained", h5ls.out$name, invert = TRUE),]
  }
  if("covariates" %in%  h5ls.out$name){
    covariate_names <- as.character( h5read(file, "covariates")[[1]])
  } else {
    covariate_names <- NULL
  }

  # Load training data (as nested list of matrices)
  data <- list(); intercepts <- list()
  if (load_data && "data"%in%h5ls.out$name) {
    
    object@data_options[["loaded"]] <- TRUE
    if (verbose) message("Loading data...")
    
    for (m in view_names) {
      data[[m]] <- list()
      intercepts[[m]] <- list()
      for (g in group_names) {
        if (on_disk) {
          # as DelayedArrays
          data[[m]][[g]] <- DelayedArray::DelayedArray( HDF5ArraySeed(file, name = sprintf("data/%s/%s", m, g) ) )
        } else {
          # as matrices
          data[[m]][[g]] <- h5read(file, sprintf("data/%s/%s", m, g) )
          tryCatch(intercepts[[m]][[g]] <- as.numeric( h5read(file, sprintf("intercepts/%s/%s", m, g) ) ), error = function(e) { NULL })
        }
        # Replace NaN by NA
        data[[m]][[g]][is.nan(data[[m]][[g]])] <- NA # this realised into memory, TO FIX
      }
    }
    
  # Create empty training data (as nested list of empty matrices, with the correct dimensions)
  } else {
    
    object@data_options[["loaded"]] <- FALSE
    
    for (m in view_names) {
      data[[m]] <- list()
      for (g in group_names) {
        data[[m]][[g]] <- .create_matrix_placeholder(rownames = feature_names[[m]], colnames = sample_names[[g]])
      }
    }
  }

  object@data <- data
  object@intercepts <- intercepts


  # Load metadata if any
  if ("samples_metadata" %in% h5ls.out$name) {
    object@samples_metadata <- bind_rows(lapply(group_names, function(g) as.data.frame(h5read(file, sprintf("samples_metadata/%s", g)))))
  }
  if ("features_metadata" %in% h5ls.out$name) {
    object@features_metadata <- bind_rows(lapply(view_names, function(m) as.data.frame(h5read(file, sprintf("features_metadata/%s", m)))))
  }
  
  # ############################
  # ## Load sample covariates ##
  # ############################
  # 
  # if (any(grepl("cov_samples", h5ls.out$group))){
  #   covariates <- list()
  #   for (g in group_names) {
  #     if (on_disk) {
  #       # as DelayedArrays
  #       covariates[[g]] <- DelayedArray::DelayedArray( HDF5ArraySeed(file, name = sprintf("cov_samples/%s", g) ) )
  #     } else {
  #       # as matrices
  #       covariates[[g]] <- h5read(file, sprintf("cov_samples/%s", g) )
  #     }    
  #   }
  # } else covariates <- NULL
  # object@covariates <- covariates

  # if (any(grepl("cov_samples_transformed", h5ls.out$group))){
  #   covariates_warped <- list()
  #   for (g in group_names) {
  #     if (on_disk) {
  #       # as DelayedArrays
  #       covariates_warped[[g]] <- DelayedArray::DelayedArray( HDF5ArraySeed(file, name = sprintf("cov_samples_transformed/%s", g) ) )
  #     } else {
  #       # as matrices
  #       covariates_warped[[g]] <- h5read(file, sprintf("cov_samples_transformed/%s", g) )
  #     }    
  #   }
  # } else covariates_warped <- NULL
  # object@covariates_warped <- covariates_warped
  
  # #######################
  # ## Load interpolated factor values ##
  # #######################
  # 
  # interpolated_Z <- list()
  # if (isTRUE(load_interpol_Z)) {
  #   
  #   if (isTRUE(verbose)) message("Loading interpolated factor values...")
  #   
  #   for (g in group_names) {
  #     interpolated_Z[[g]] <- list()
  #     if (on_disk) {
  #       # as DelayedArrays
  #       # interpolated_Z[[g]] <- DelayedArray::DelayedArray( HDF5ArraySeed(file, name = sprintf("Z_predictions/%s", g) ) )
  #     } else {
  #       # as matrices
  #       tryCatch( {
  #         interpolated_Z[[g]][["mean"]] <- h5read(file, sprintf("Z_predictions/%s/mean", g) )
  #       }, error = function(x) { print("Predicitions of Z not found, not loading it...") })
  #       tryCatch( {
  #         interpolated_Z[[g]][["variance"]] <- h5read(file, sprintf("Z_predictions/%s/variance", g) )
  #       }, error = function(x) { print("Variance of predictions of Z not found, not loading it...") })
  #       tryCatch( {
  #         interpolated_Z[[g]][["new_values"]] <- h5read(file, "Z_predictions/new_values")
  #       }, error = function(x) { print("New values of Z not found, not loading it...") })
  #     }
  #   }
  # }
  # object@interpolated_Z <- interpolated_Z
  
  #######################
  ## Load expectations ##
  #######################

  expectations <- list()
  node_names <- h5ls.out[h5ls.out$group=="/expectations","name"]

  if (verbose) message(paste0("Loading expectations for ", length(node_names), " nodes..."))

  if ("AlphaW" %in% node_names)
    expectations[["AlphaW"]] <- h5read(file, "expectations/AlphaW")[view_names]
  if ("AlphaZ" %in% node_names)
    expectations[["AlphaZ"]] <- h5read(file, "expectations/AlphaZ")[group_names]
  if ("Sigma" %in% node_names)
    expectations[["Sigma"]] <- h5read(file, "expectations/Sigma")
  if ("Z" %in% node_names)
    expectations[["Z"]] <- h5read(file, "expectations/Z")[group_names]
  if ("W" %in% node_names)
    expectations[["W"]] <- h5read(file, "expectations/W")[view_names]
  if ("ThetaW" %in% node_names)
    expectations[["ThetaW"]] <- h5read(file, "expectations/ThetaW")[view_names]
  if ("ThetaZ" %in% node_names)
    expectations[["ThetaZ"]] <- h5read(file, "expectations/ThetaZ")[group_names]
  # if ("Tau" %in% node_names)
  #   expectations[["Tau"]] <- h5read(file, "expectations/Tau")
  
  object@expectations <- expectations

  
  ########################
  ## Load model options ##
  ########################

  if (verbose) message("Loading model options...")

  tryCatch( {
    object@model_options <- as.list(h5read(file, 'model_options', read.attributes = TRUE))
  }, error = function(x) { print("Model options not found, not loading it...") })

  # Convert True/False strings to logical values
  for (i in names(object@model_options)) {
    if (object@model_options[i] == "False" || object@model_options[i] == "True") {
      object@model_options[i] <- as.logical(object@model_options[i])
    } else {
      object@model_options[i] <- object@model_options[i]
    }
  }

  ##########################################
  ## Load training options and statistics ##
  ##########################################

  if (verbose) message("Loading training options and statistics...")

  # Load training options
  if (length(object@training_options) == 0) {
    tryCatch( {
      object@training_options <- as.list(h5read(file, 'training_opts', read.attributes = TRUE))
    }, error = function(x) { print("Training opts not found, not loading it...") })
  }

  # Load training statistics
  tryCatch( {
    object@training_stats <- h5read(file, 'training_stats', read.attributes = TRUE)
    object@training_stats <- h5read(file, 'training_stats', read.attributes = TRUE)
  }, error = function(x) { print("Training stats not found, not loading it...") })

  #############################
  ## Load covariates options ##
  #############################
  # 
  # if (any(grepl("cov_samples", h5ls.out$group))) { 
  #   if (isTRUE(verbose)) message("Loading covariates options...")
  #   tryCatch( {
  #     object@mefisto_options <- as.list(h5read(file, 'smooth_opts', read.attributes = TRUE))
  #   }, error = function(x) { print("Covariates options not found, not loading it...") })
  #   
  #   # Convert True/False strings to logical values
  #   for (i in names(object@mefisto_options)) {
  #     if (object@mefisto_options[i] == "False" | object@mefisto_options[i] == "True") {
  #       object@mefisto_options[i] <- as.logical(object@mefisto_options[i])
  #     } else {
  #       object@mefisto_options[i] <- object@mefisto_options[i]
  #     }
  #   }
  #   
  # }
  # 
  
    
  #######################################
  ## Load variance explained estimates ##
  #######################################
  
  if ("variance_explained" %in% h5ls.out$name) {
    r2_list <- list(
      r2_total = h5read(file, "variance_explained/r2_total")[group_names],
      r2_per_factor = h5read(file, "variance_explained/r2_per_factor")[group_names]
    )
    object@cache[["variance_explained"]] <- r2_list
  }
  
  # Hack to fix the problems where variance explained values range from 0 to 1 (%)
  if (max(sapply(object@cache$variance_explained$r2_total,max,na.rm=TRUE),na.rm=TRUE)<1) {
    for (m in 1:length(view_names)) {
      for (g in 1:length(group_names)) {
        object@cache$variance_explained$r2_total[[g]][[m]] <- 100 * object@cache$variance_explained$r2_total[[g]][[m]]
        object@cache$variance_explained$r2_per_factor[[g]][,m] <- 100 * object@cache$variance_explained$r2_per_factor[[g]][,m]
      }
    }
  }
  
  ##############################
  ## Specify dimensionalities ##
  ##############################
  
  # Specify dimensionality of the data
  object@dimensions[["M"]] <- length(data)                            # number of views
  object@dimensions[["G"]] <- length(data[[1]])                       # number of groups
  object@dimensions[["N"]] <- sapply(data[[1]], ncol)                 # number of samples (per group)
  object@dimensions[["D"]] <- sapply(data, function(e) nrow(e[[1]]))  # number of features (per view)
  # object@dimensions[["C"]] <- nrow(covariates[[1]])                        # number of covariates
  object@dimensions[["K"]] <- ncol(object@expectations$Z[[1]])        # number of factors
  
  # Assign sample and feature names (slow for large matrices)
  if (verbose) message("Assigning names to the different dimensions...")

  # Create default features names if they are null
  if (is.null(feature_names)) {
    print("Features names not found, generating default: feature1_view1, ..., featureD_viewM")
    feature_names <- lapply(seq_len(object@dimensions[["M"]]),
                            function(m) sprintf("feature%d_view_&d", as.character(seq_len(object@dimensions[["D"]][m])), m))
  } else {
    # Check duplicated features names
    all_names <- unname(unlist(feature_names))
    duplicated_names <- unique(all_names[duplicated(all_names)])
    if (length(duplicated_names)>0) 
      warning("There are duplicated features names across different views. We will add the suffix *_view* only for those features 
            Example: if you have both TP53 in mRNA and mutation data it will be renamed to TP53_mRNA, TP53_mutation")
    for (m in names(feature_names)) {
      tmp <- which(feature_names[[m]] %in% duplicated_names)
      if (length(tmp)>0) feature_names[[m]][tmp] <- paste(feature_names[[m]][tmp], m, sep="_")
    }
  }
  features_names(object) <- feature_names
  
  # Create default samples names if they are null
  if (is.null(sample_names)) {
    print("Samples names not found, generating default: sample1, ..., sampleN")
    sample_names <- lapply(object@dimensions[["N"]], function(n) paste0("sample", as.character(seq_len(n))))
  }
  samples_names(object) <- sample_names

  # Add covariates names
  # if(!is.null(object@covariates)){
  #   # Create default covariates names if they are null
  #   if (is.null(covariate_names)) {
  #     print("Covariate names not found, generating default: covariate1, ..., covariateC")
  #     covariate_names <- paste0("sample", as.character(seq_len(object@dimensions[["C"]])))
  #   }
  #   covariates_names(object) <- covariate_names
  # }
  
  # Set views names
  if (is.null(names(object@data))) {
    print("Views names not found, generating default: view1, ..., viewM")
    view_names <- paste0("view", as.character(seq_len(object@dimensions[["M"]])))
  }
  views_names(object) <- view_names
  
  # Set groups names
  if (is.null(names(object@data[[1]]))) {
    print("Groups names not found, generating default: group1, ..., groupG")
    group_names <- paste0("group", as.character(seq_len(object@dimensions[["G"]])))
  }
  groups_names(object) <- group_names
  
  # Set factors names
  factors_names(object)  <- paste0("Factor", as.character(seq_len(object@dimensions[["K"]])))
  
  ###################
  ## Parse factors ##
  ###################
  
  # Calculate variance explained estimates per factor
  if (is.null(object@cache[["variance_explained"]])) {
    object@cache[["variance_explained"]] <- calculate_variance_explained(object)
  } 
  
  # Remove inactive factors
  if (remove_inactive_factors) {
    r2 <- rowSums(do.call('cbind', lapply(object@cache[["variance_explained"]]$r2_per_factor, rowSums, na.rm=TRUE)))
    var.threshold <- 0.0001
    if (all(r2 < var.threshold)) {
      warning(sprintf("All %s factors were found to explain little or no variance so remove_inactive_factors option has been disabled.", length(r2)))
    } else if (any(r2 < var.threshold)) {
      object <- subset_factors(object, which(r2>=var.threshold))
      message(sprintf("%s factors were found to explain no variance and they were removed for downstream analysis. You can disable this option by setting load_model(..., remove_inactive_factors = FALSE)", sum(r2 < var.threshold)))
    }
  }
  
  # [Done in mofapy2] Sort factors by total variance explained
  if (sort_factors && object@dimensions$K>1) {

    # Sanity checks
    if (verbose) message("Re-ordering factors by their variance explained...")

    # Calculate variance explained per factor across all views
    r2 <- rowSums(sapply(object@cache[["variance_explained"]]$r2_per_factor, function(e) rowSums(e, na.rm = TRUE)))
    order_factors <- c(names(r2)[order(r2, decreasing = TRUE)])

    # re-order factors
    object <- subset_factors(object, order_factors)
  }

  # Mask outliers
  if (remove_outliers) {
    if (verbose) message("Removing outliers...")
    object <- .detect_outliers(object)
  }
  
  # Mask intercepts for non-Gaussian data
  if (any(object@model_options$likelihoods!="gaussian")) {
    for (m in names(which(object@model_options$likelihoods!="gaussian"))) {
      for (g in names(object@intercepts[[m]])) {
        object@intercepts[[m]][[g]] <- NA
      }
    }
  }

  # ######################
  # ## Quality controls ##
  # ######################
  # 
  # if (verbose) message("Doing quality control...")
  # object <- .quality_control(object, verbose = verbose)
  # 
  return(object)
}

mofa_trained <- load_model(outfile)

samples_names(mofa_trained) <- samples_names(object)
samples_metadata(mofa_trained)
rownames(samples_metadata(mofa_trained)) <- samples_metadata(mofa_trained)[["sample"]]

Prune factors

Visualize variance explained by factors

get_variance_explained(mofa_trained, as.data.frame = TRUE, )[[1]] %>%
  dplyr::mutate(group=factor(group, levels=rev(anno_order))) %>% 
  ggplot(aes(factor,group, fill=value)) +
  geom_tile() +
  scale_fill_viridis_c() +
  theme(axis.text.x = element_text(angle=45, hjust=1))

get_variance_explained(mofa_trained, as.data.frame = TRUE, )[[2]] %>%
  dplyr::mutate(group=factor(group, levels=anno_order)) %>%
  ggplot(aes(group, value)) +
  geom_col() +
  coord_flip() +
  ylab("Var. (%)") +
  theme_classic(base_size=14)

Identify technical factors

Do factors relate to the number of cells in pseudobulk?

n_cells <- mofa_trained@samples_metadata[,'n_cells', drop=FALSE]
Z <- get_factors(mofa_trained)
Z <- purrr::reduce(Z, rbind)
barplot(cor(n_cells, Z))

Distinguish technical factors by weight sparsity

get_weights(mofa_trained, abs=TRUE, scale = FALSE, as.data.frame = TRUE) %>%
  dplyr::group_by(factor) %>%
  dplyr::mutate(value=(value - min(value))/(max(value)- min(value)), rank=rank(value)) %>%
  dplyr::summarise(frac_zeros=sum(value < 0.05)/dplyr::n()) %>%
  ggplot(aes(factor, frac_zeros)) +
  geom_col() +
  coord_flip() +
  geom_hline(yintercept = 0.5, color="red")
exclude_factors <- get_weights(mofa_trained, abs=TRUE, scale = FALSE, as.data.frame = TRUE) %>%
  dplyr::group_by(factor) %>%
  dplyr::mutate(value=(value - min(value))/(max(value)- min(value)), rank=rank(value)) %>%
  dplyr::summarise(frac_zeros=sum(value < 0.05)/dplyr::n()) %>%
  dplyr::filter(frac_zeros < 0.55) %>%
  dplyr::pull(factor)
high_r2_groups_df <- get_variance_explained(mofa_trained, as.data.frame = TRUE)[[1]] %>%
  dplyr::filter(!factor %in% exclude_factors) %>%
  dplyr::group_by(group) %>%
  dplyr::mutate(tot_var=sum(value)) %>%
  dplyr::ungroup() %>%
  dplyr::arrange(tot_var) %>%
  dplyr::mutate(group=factor(group, levels = unique(group))) %>%
  dplyr::mutate(value=ifelse(value < 5, NA, value)) %>%
  dplyr::filter(!is.na(value)) 

high_r2_groups_df %>%
  ggplot(aes(factor,group, fill=value)) +
  geom_tile() +
  scale_fill_gradientn(colors=c("gray97","darkblue"), guide="colorbar") +
  theme_classic() +
  theme(axis.text.x = element_text(angle=45, hjust=1)) 
  

Find factors that separate pseudobulks by tissue

Calculating Adjusted Mutual Information between organ identity and clustering of pseudobulks based on factor values

calc_organ_AMI <- function(f, g){
  dmat <- dist(get_factors(mofa_trained, factors = f, groups = g)[[1]]) 
  hcl <- hclust(dmat)
  n_organs <- length(unique(samples_metadata(mofa_trained)[rownames(as.matrix(dmat)),'organ']))
  hcl_df <- data.frame(clust=cutree(hcl, k=n_organs)) %>%
    tibble::rownames_to_column("sample") %>%
    dplyr::left_join(samples_metadata(mofa_trained)) 
  organ_AMI <- aricode::AMI(hcl_df$clust, as.numeric(hcl_df$organ))
  return(organ_AMI)
}

samples_metadata(mofa_trained)$organ <- as.factor(samples_metadata(mofa_trained)$organ)
## Calc adjusted mutual info for each factor
AMIs <- sapply(1:nrow(high_r2_groups_df), function(i) calc_organ_AMI(high_r2_groups_df$factor[i], high_r2_groups_df$group[i]))

## Calc adjusted mutual info for all factors that explan > 2% variance 
groups <- as.character(unique(high_r2_groups_df$group))
ct_AMIs <- sapply(groups, function(g) calc_organ_AMI(high_r2_groups_df$factor[high_r2_groups_df$group == g], g))

AMI_pl <- data.frame(ct_AMIs) %>%
  dplyr::arrange(ct_AMIs) %>%
  tibble::rownames_to_column("celltype") %>%
  # dplyr::mutate(celltype=factor(rowname, levels=unique(rowname))) %>%
dplyr::mutate(celltype=factor(celltype, levels=rev(anno_order))) %>%
  ggplot(aes(ct_AMIs, celltype)) +
  geom_col() +
  xlab("Total Organ AMI") +
  theme_classic(base_size = 16)
  
AMI_f_pl <- high_r2_groups_df %>%
  dplyr::mutate(org_AMI=AMIs) %>%
  dplyr::mutate(group=factor(group, levels=levels(AMI_pl$data$celltype))) %>%
  ggplot(aes(factor, group, fill=org_AMI)) +
  geom_tile(color='black') +
  # scale_fill_gradientn(colors=c("gray97","red"), name="Organ Adj. Mutual Info") +
  scale_fill_viridis_c(option='magma') +
  theme_classic(base_size = 16) +
  theme(axis.text.x = element_text(angle=45, hjust=1))   

AMI_f_pl + (AMI_pl + remove_y_axis()) +
  plot_layout(guides="collect", widths = c(8,3))
## Save info on MI
high_r2_groups_df <- high_r2_groups_df %>%
  dplyr::mutate(org_AMI=AMIs) 

## Fix long names for plotting
all_groups <- names(get_data(mofa_trained)[[1]])
group_labeller <- all_groups %>%
  str_replace_all("_", " ") %>%
  {ifelse(nchar(.) > 20, str_replace(., " ", "\n"), .)} %>%
  setNames(all_groups)

AMI_pl_df <- high_r2_groups_df %>%
  dplyr::group_by(group) %>%
  dplyr::mutate(mean_AMI=max(org_AMI)) %>%
  dplyr::ungroup() %>%
  dplyr::arrange(mean_AMI) %>%
  dplyr::mutate(group=group_labeller[as.character(group)]) %>%
  dplyr::mutate(group=factor(group, levels=unique(group))) 

AMI_pl_df %>%
  ggplot(aes(org_AMI, group)) +
  geom_point(aes(fill=value), size=3, shape=21) +
  ggrepel::geom_text_repel(aes(label=str_remove(factor, "Factor")), color="black", force = 0.1, direction = 'x',
                           nudge_y           = 0.4,
    hjust             = 0) +
  xlab("Adj. Mutual Information - Organ ") +
  scale_fill_gradientn(colours = c("white", "red"), name="% var. explained") +
  theme_bw(base_size = 15)
for (fact in as.character(unique(AMI_pl_df$factor))){
  p <- AMI_pl_df %>%
    ggplot(aes(org_AMI, group)) +
    geom_point(fill="grey", size=2, shape=21, color="grey") +
    geom_point(data = . %>% dplyr::filter(factor==fact),
                 aes(fill=value), size=3, shape=21) +
    xlab("Adj. Mutual Information - Organ ") +
    scale_fill_gradientn(colours = c("white", "red"), name="% var. explained") +
    theme_bw(base_size = 15) +
    ggtitle(fact)
  print(p)
  }

Expression of top R2 factors

get_top_weight_genes <- function(mofa_trained, f, n_top=20, which="top"){
  w_df <- get_weights(mofa_trained, factors = f, as.data.frame = TRUE) %>%
    dplyr::arrange(value) 
  top_genes <- w_df %>%
      dplyr::top_n(n_top, value) %>%
      dplyr::pull(feature) %>%
      as.character()
  bot_genes <-  w_df %>%
      dplyr::top_n(n_top, -value) %>%
      dplyr::pull(feature) %>%
      as.character()
  if (which=="top") {
    genes <- top_genes
  } else if (which=="bottom"){
    genes <- bot_genes
  } else if (which=="both"){
    genes <- c(top_genes, bot_genes)
  }
  return(genes)
}

plot_data_top_weights <- function(mofa_trained, ct, f, n_top=20, which="top"){
  genes <- get_top_weight_genes(mofa_trained, f, which=which, n_top=n_top)
  data <- get_data(mofa_trained, groups=ct)[[1]][[1]][genes,]
  
  pl_df <- reshape2::melt(data, varnames=c("gene", "sample")) %>%
    dplyr::left_join(samples_metadata(mofa_trained)) %>%
    dplyr::arrange(age) %>%
    dplyr::mutate(sample=factor(sample, levels=unique(sample))) %>%
    dplyr::group_by(gene) %>%
    dplyr::mutate(value=scale(value))
  pl_df %>%
    ggplot(aes(sample, gene, fill=value)) +
    geom_tile() +
    facet_grid(.~organ, space="free", scales="free") +
    scale_fill_gradient2(high="red", low="blue", name="Scaled\nexpression") +
    xlab("----age--->") + ylab(glue("{which} weight genes")) +
    theme_bw(base_size=16) +
    theme(axis.ticks.x = element_blank(), axis.text.x = element_blank()) +
    ggtitle(glue('{ct} - {f}'))
}

for (g in all_groups){
  fs <- get_top_factor_per_celltype(mofa_trained, g, min_R2=5)
  fs <- fs[!fs %in% exclude_factors]
  if (length(fs) > 0){
    top_plots <- lapply(fs, function(x) (plot_data_top_weights(mofa_trained, g, x, which="top") + remove_x_axis()) /  
                          plot_data_top_weights(mofa_trained, g, x, which="bottom") + ggtitle("")
    )
    full_pl <-wrap_plots(top_plots, ncol=1) 
    ggsave(glue("{figdir}/top_factors_expr_{g}.pdf"),plot=full_pl,  width=12, height = 10*length(top_plots))
    }  
}
minmax_normalize <- function(x, na.rm = TRUE) {
    return((x- min(x)) /(max(x)-min(x)))
}

plot_data_top_weights_clustered <- function(mofa_trained, cts, f, n_top=20, which="top", scale_data=TRUE){
  genes <- get_top_weight_genes(mofa_trained, f, which=which, n_top=n_top)
  
  genes_anno <- data.frame(gene=genes) 
  if (which!="both"){ genes_anno[["weight"]] <- rep(which, n_top) }  else { genes_anno[["weight"]] <- c(rep("top", n_top), rep("bottom", n_top)) }
  
  data_ls <- get_data(mofa_trained, groups=cts)[[1]]
  data <- Reduce(cbind, data_ls)[genes,]
  
  ct_pl_ls <- lapply(cts, function(ct){
    ct_samples <- colnames(mofa_trained@data[[1]][[ct]])
    ct_data <- data[,ct_samples]
    if (scale_data){
      ct_data <- t(apply(ct_data, 1, minmax_normalize))
    }
    cl_heatmap <- pheatmap::pheatmap(ct_data, show_colnames= FALSE, cluster_rows = FALSE, )
    col_order <- cl_heatmap$tree_col$labels[cl_heatmap$tree_col$order]
    
    pl_df <- reshape2::melt(ct_data, varnames=c("gene", "sample")) %>%
        dplyr::left_join(samples_metadata(mofa_trained)) %>%
        dplyr::left_join(genes_anno) %>%
        dplyr::mutate(sample=factor(sample, levels=col_order),
                      weight=factor(weight, levels=c("top", "bottom"))) 
    
    pl_bar <- pl_df %>% 
      ggplot(aes(sample, "organ", fill=organ)) +
      geom_tile() +
      scale_fill_manual(values=org_colors) +
      theme_void() +
      theme(legend.position = "none")
    pl_hm <- pl_df %>%
      ggplot(aes(sample, gene, fill=value)) +
        geom_tile() +
        scale_fill_viridis_c(option="magma", name="Scaled\nexpression") +
        xlab(group_labeller[ct]) +
        facet_grid(weight~., scales="free", space="free") +
        theme_bw(base_size=12) +
        theme(axis.ticks.x = element_blank(), axis.text.x = element_blank())
    (pl_bar / pl_hm) + plot_layout(heights = c(1,10))
    })
  if (length(ct_pl_ls) > 1){
    ## Remove gene names to all except 1st plot
    ct_pl_ls[2:length(ct_pl_ls)] <- lapply(ct_pl_ls[2:length(ct_pl_ls)], function(p) p + remove_y_axis())
    
    ## Remove strip names to all except last plot
    ct_pl_ls[1:(length(ct_pl_ls)-1)] <- lapply(ct_pl_ls[1:(length(ct_pl_ls)-1)], function(p) p + theme(strip.background = element_blank(),strip.text.y = element_blank()))
    wrap_plots(ct_pl_ls) + 
    plot_layout(guides="collect", nrow = 1)
  } else {
    ct_pl_ls[[1]]
  }
}

plot_factor_organ_boxplots <- function(f, cts){
  pl_ls <- lapply(cts, function(g) plot_factor(mofa_trained,groups = c(g), color_by="organ", dot_size = 3, factors = f,
            add_boxplot = TRUE, boxplot_alpha = 0.1, 
            group_by = 'group', dodge = TRUE) +
    scale_fill_manual(values=org_colors) +
    scale_color_manual(values=org_colors) +
    ggtitle(group_labeller[g]) 
    )
 wrap_plots(pl_ls) + 
   plot_layout(guides="collect", nrow=1) + 
   plot_annotation(title=f) 
}

high_r2_groups_df_filt <- high_r2_groups_df %>%
  dplyr::filter(org_AMI > 0.3) %>%
  dplyr::arrange(- org_AMI) 

for (fact in as.character(unique(high_r2_groups_df_filt$factor))){
  fact_cts = as.character(high_r2_groups_df_filt$group[high_r2_groups_df_filt$factor==fact])
  p_top <- plot_factor_organ_boxplots(cts=fact_cts, f=fact)
  p_bottom <- plot_data_top_weights_clustered(mofa_trained, cts=fact_cts, f=fact, which = "both", scale_data = TRUE)
  f_pl <- (p_top / p_bottom) +
    plot_layout(heights = c(1,2.5))
  
  ggsave(glue("{figdir}/{fact}_top_organ_AMI_plot.pdf"), plot=f_pl,  width=5 + (3*length(fact_cts)), height = 9)
}

—

get_factors(mofa_trained, as.data.frame = TRUE) %>%
  left_join(samples_metadata(mofa_trained)) %>%
  mutate(sort=ifelse(str_detect(Sample, "CD45P"), "CD45+", ifelse(str_detect(Sample,"CD45N"), "CD45-", ifelse(str_detect(Sample, "TOT"), "TOT", "other")))) %>%
  filter(organ=="TH" & factor=="Factor2" & group=="CD8AA") %>%
  ggplot(aes(value,Sample,  color=sort)) +
  geom_point() +
  ggtitle("TREG") +
  xlab("Factor2") 
p1 <- get_factors(mofa_trained, as.data.frame = TRUE) %>%
  left_join(samples_metadata(mofa_trained)) %>%
  filter(organ=="TH" & factor=="Factor2" & group=="TREG") %>%
  ggplot(aes(value,Sample,  color=age)) +
  geom_point() +
  scale_color_viridis_c() +
  ggtitle("TREG") +
  xlab("Factor2") 

p2 <- get_factors(mofa_trained, as.data.frame = TRUE) %>%
  left_join(samples_metadata(mofa_trained)) %>%
  filter(organ=="TH" & factor=="Factor2" & group=="TH17") %>%
  ggplot(aes(value,Sample,  color=age)) +
  geom_point() +
  scale_color_viridis_c() +
  ggtitle("TH17") +
  xlab("Factor2") 

p3 <- get_factors(mofa_trained, as.data.frame = TRUE) %>%
  left_join(samples_metadata(mofa_trained)) %>%
  filter(organ=="TH" & factor=="Factor2" & group=="CD8AA") %>%
  ggplot(aes(value,Sample,  color=age)) +
  geom_point() +
  scale_color_viridis_c() +
  ggtitle("CD8AA") +
  xlab("Factor2")

(p1 / p2 /p3) + plot_layout(guides='collect')

Visualize variance explained by factors

plot_variance_explained(mofa_trained, x='factor', y='group', split_by = 'view', plot_total = TRUE, max_r2 = 50)[[1]] +
  theme(axis.text.x = element_text(angle=45, hjust=1))

get_variance_explained(mofa_trained, as.data.frame = TRUE)[[2]] %>%
  ggplot(aes(group, value)) +
  geom_col() +
  coord_flip() +
  ylab("Var. (%)") +
  theme_classic(base_size=14)

Plot by celltype

get_variance_explained(mofa_trained, as.data.frame = TRUE)[[1]] %>%
  ggplot(aes(factor, value)) + geom_col() +
  coord_flip() +
  facet_wrap(group~., ncol = 6, scales = "free_x")
plot_factor_cor(mofa_trained, method = "spearman")
## Correlation with principal components
pcs <- reducedDim(sce)
fctrs <- get_factors(mofa_trained) %>%
  purrr::reduce(rbind)

corrplot::corrplot(cor(pcs, fctrs[rownames(pcs),]))

Factor ID plots

plot_factor_ordered <- function(mofa_trained, f){
  factor_df <- get_factors(mofa_trained, factors = f, as.data.frame = TRUE) %>%
      mutate(organ = sapply(str_split(sample, "_"), function(x) x[2])) %>%
      group_by(group) %>%
      mutate(gr_mean = median(value)) %>%
      ungroup() %>%
      arrange(gr_mean) %>%
      mutate(group=factor(group, levels=unique(group))) 
  
  r2_df <- get_variance_explained(mofa_trained, factors = f, as.data.frame = TRUE)[[1]] %>%
    filter(factor==paste0('Factor',f)) %>%
    mutate(group=factor(group, levels = levels(factor_df$group)))
  
  pl1 <- factor_df %>%
      ggplot(aes(group, value)) +
      geom_boxplot() +
      geom_jitter(aes(color= organ), size=0.7) +
      geom_hline(yintercept = 0, linetype=2) +
      coord_flip() +
      ylab(paste0("Factor ", f)) +
      theme_bw(base_size = 14)
  
  pl2 <- r2_df %>%
    ggplot(aes(group, value)) +
    geom_col() +
    coord_flip() +
    ylab("% variance explained") +
    theme_bw(base_size = 14) +
    remove_y_axis()
  
  pl1 + pl2 + plot_layout(widths=c(2,1), guides="collect") 
}

get_top_celltype_per_factor <- function(mofa_trained, f){
  r2_df <- get_variance_explained(mofa_trained, factors = f, as.data.frame = TRUE)[[1]] %>%
    filter(factor==paste0('Factor',f)) 
    # mutate(group=factor(group, levels = ))
  top_quant_r2 <- quantile(r2_df$value, probs = seq(0, 1, by = 0.2))["80%"]
  top_groups <- r2_df$group[r2_df$value >= top_quant_r2]
  return(top_groups)
}

save_factor_id <- function(mofa_trained, f, figdir){
  ## Order celltypes by factor values
  p1 <- plot_factor_ordered(mofa_trained, f)
  
  ## Plot factor values across organs for celltypes with high variance explained
  p2 <- plot_factor(mofa_trained, factors = f, groups = get_top_celltype_per_factor(mofa_trained, f), group_by = "group", 
              color_by = "organ", 
              dot_size = 2, dodge = TRUE
              )
  
  ## Plot factor weights on genes
  # plot_data_heatmap(mofa_trained, factor = f, nfeatures = 50, text_size = 3, show_colnames=FALSE,
  #                   annotation_samples = c("organ", "time", "method", "donor"))
  p3 <- plot_weights(mofa_trained, factors = f, nfeatures = 30, text_size = 3) +
   scale_y_discrete(expand=c(0.1, 0.1))
  
  full_pl <- (p1 | (p2 / p3)) +
    plot_layout(guides="collect") 
  ggsave(glue("{figdir}/MOFA_{split}_factorID_factor{f}.pdf"), plot=full_pl, width = 15, height = 10)
}

for (f in 1:mofa_trained@dimensions$K){
  print(paste0("Saving ID for Factor ", f, "..."))
  save_factor_id(mofa_trained, f=f, figdir = figdir)  
}

# save_factor_id(mofa_trained, f=1, figdir = figdir)  
# plot_weights(mofa_trained, factors = f, nfeatures = 30, text_size = 3) +
#    scale_y_discrete(expand=c(0.1, 0.1))

KNN graph per celltype

## Get factors that explain most variance in each celltype
get_top_factor_per_celltype <- function(mofa_trained, gr, min_R2=2){
  get_variance_explained(mofa_trained, as.data.frame = TRUE)[[1]] %>%
    filter(group==gr) %>%
    filter(value >= min_R2) %>%
    pull(factor) %>%
    as.character()
}

## Make KNN graph based on similarity of top factors for each celltype
get_ct_KNN_graph <- function(mofa_trained, gr, min_R2=5, k=5){
  ## Get factors that explain most variance per celltype
  fs <- get_top_factor_per_celltype(mofa_trained, gr, min_R2 = min_R2)
  
  ## Exclude factor1 (proliferation)
  fs <- fs[!fs %in% c("Factor1", "Factor2")]
  
  ## Make KNN graph from top factors
  Z <- get_factors(mofa_trained, groups=gr, factors = fs)[[1]]
  knn_ct <- buildKNNGraph(t(Z), k=k)
  
  ## Add attributes
  metadata_ct <- samples_metadata(mofa_trained)[rownames(Z),]
  # covariates
  V(knn_ct)$organ <- metadata_ct$organ
  V(knn_ct)$age <- metadata_ct$age
  V(knn_ct)$n_cells <- metadata_ct$n_cells
  V(knn_ct)$method <- metadata_ct$method
  V(knn_ct)$donor <- metadata_ct$donor
  # top factors
  for (c in colnames(Z)){
   vertex_attr(knn_ct)[[c]] <- Z[,c]  
  }
  
  return(knn_ct)
  }

## Plot KNN graph
plot_ct_KNN_graph <- function(knn, color_by="organ"){
  ## Define color 
  if (!color_by %in% names(vertex_attr(knn))){
    stop("specified color_by variable is not in vertex_attr(knn)")
  }
  
  if (color_by=="organ"){ 
    scale_color_knngraph <- scale_color_manual(values=org_colors)
  } else if (is.numeric(vertex_attr(knn, color_by))){
    scale_color_knngraph <- scale_color_viridis_c(option="magma")  
  } else {
      scale_color_knngraph <- scale_color_discrete()
    }
  
  vertex_attr(knn, "color_by") <- vertex_attr(knn, color_by)
  
  ggraph(knn) +
    geom_edge_link0() +
    geom_node_point(aes(color=color_by, size=n_cells)) +
    theme(panel.background = element_blank()) +
    scale_color_knngraph +
    scale_size(range=c(2,7)) 
  }

get_top_factor_per_celltype(mofa_trained, "NK")

all_groups <- names(get_data(mofa_trained)[[1]])
knn_graph_pl <- lapply(all_groups, function(g){
  knn <- get_ct_KNN_graph(mofa_trained, g, k=5, min_R2 = 2)
  plot_ct_KNN_graph(knn, color_by = 'organ') + ggtitle(g)
  })

knn_graph_pl <- setNames(knn_graph_pl, all_groups)
knn <- get_ct_KNN_graph(mofa_trained, 'B1', k=5, min_R2 = 1)
plot_ct_KNN_graph(knn, color_by = 'Factor5') 
plot_factor(mofa_trained,groups = 'MATURE_B', factors = c(5), group_by ='organ', color_by = "age")
## Score connectivity between samples from the same organ
.calc_connectivity_score <- function(knn, o){
  adj <- get.adjacency(knn)
  n_org <- sum(V(knn)$organ==o)
  n_other <- sum(V(knn)$organ!=o)
  within_edges <- sum(adj[V(knn)$organ==o,V(knn)$organ==o])
  between_edges <- sum(adj[V(knn)$organ==o,V(knn)$organ!=o])
  score <- (within_edges/between_edges)*(n_other/n_org)
  return(score)
  }

## Calculate connectivity score for permutations of node labels
conn_score_test <- function(knn, o, n_perm=1000){
  real_score <- .calc_connectivity_score(knn, o)
  ## Random permutations
  rand_scores <- c()
  for (i in 1:n_perm){
    rand_knn <- knn
    V(rand_knn)$organ <- sample(V(knn)$organ)
    rand_scores <- c(rand_scores, .calc_connectivity_score(rand_knn, o))   
  }
  
  p_val <- sum(c(rand_scores, real_score) >= real_score)/(n_perm + 1)
  if (p_val < 2e-16){ p_val <- 2e-16}
  return(c('score'=real_score,'p_value'=p_val))
}

## Calculate connectivity score + significance with permutation test
test_conn_group <- function(mofa_trained, g, k=5, min_R2 = 2, n_perm=1000){
  knn <- get_ct_KNN_graph(mofa_trained, g, k=k, min_R2 = min_R2)
  test_orgs <- names(table(V(knn)$organ))[table(V(knn)$organ) > 2]
  return(sapply(test_orgs, function(o) conn_score_test(knn, o, n_perm=n_perm)))
  }

connectivity_test_ls <- lapply(all_groups, function(g) test_conn_group(mofa_trained, g))
connectivity_test_ls <- setNames(connectivity_test_ls, all_groups)

connectivity_test_df <- imap(connectivity_test_ls, ~ data.frame(t(.x)) %>% rownames_to_column("organ") %>% mutate(group=.y)) %>%
  purrr::reduce(bind_rows) %>%
  mutate(is_signif = ifelse(p_value < 0.01, TRUE, FALSE)) 

connectivity_test_df %>%
  ggplot(aes(organ, group,fill=log10(score))) +
  geom_tile() +
  scale_fill_distiller(palette="Reds", direction = 1) +
  geom_text(data=. %>% filter(is_signif), label="*", size=5)
connectivity_test_df %>%
  group_by(group) %>%
  mutate(mean_val=median(score)) %>%
  ungroup() %>%
  arrange(-mean_val) %>%
  mutate(group=factor(group, levels=unique(group))) %>%
  ggplot(aes(organ, log1p(score))) +
  geom_col(fill="grey") +
  geom_col(data=. %>% filter(is_signif), aes(fill=organ)) +
  scale_fill_manual(values=org_colors)  +
  coord_flip() +
  facet_grid(group~.) +
  theme(strip.text.y = element_text(angle=0))

Expression of top R2 factors

get_top_weight_genes <- function(mofa_trained, f, n_top=20, which="top"){
  w_df <- get_weights(mofa_trained, factors = f, as.data.frame = TRUE) %>%
    arrange(value) 
  if (which=="top") {
    w_df %>%
      top_n(n_top, value) %>%
      pull(feature) %>%
      as.character()
  } else if (which=="bottom"){
    w_df %>%
      top_n(n_top, -value) %>%
      pull(feature) %>%
      as.character()
    }
}

plot_data_top_weights <- function(mofa_trained, ct, f, n_top=20, which="top"){
  genes <- get_top_weight_genes(mofa_trained, f, which=which, n_top=n_top)
  data <- get_data(mofa_trained, groups=ct)[[1]][[1]][genes,]
  
  pl_df <- reshape2::melt(data, varnames=c("gene", "sample")) %>%
    left_join(samples_metadata(mofa_trained)) %>%
    arrange(age) %>%
    mutate(sample=factor(sample, levels=unique(sample))) %>%
    group_by(gene) %>%
    mutate(value=scale(value))
  pl_df %>%
    ggplot(aes(sample, gene, fill=value)) +
    geom_tile() +
    facet_grid(.~organ, space="free", scales="free") +
    scale_fill_gradient2(high="red", low="blue", name="Scaled\nexpression") +
    xlab("----age--->") + ylab(glue("{which} weight genes")) +
    theme_bw(base_size=16) +
    theme(axis.ticks.x = element_blank(), axis.text.x = element_blank()) +
    ggtitle(glue('{ct} - {f}'))
}

for (g in all_groups){
  fs <- get_top_factor_per_celltype(mofa_trained, g, min_R2=3)
  top_plots <- lapply(fs, function(x) (plot_data_top_weights(mofa_trained, g, x, which="top") + remove_x_axis()) /  
                        plot_data_top_weights(mofa_trained, g, x, which="bottom") + ggtitle("")
  )
  full_pl <-wrap_plots(top_plots, ncol=1) 
  ggsave(glue("{figdir}/top_factors_expr_{g}.pdf"),plot=full_pl,  width=12, height = 7*length(top_plots))
}
plot_data_heatmap(mofa_trained, factor = 5, groups = "MATURE_B", scale="row", annotation_samples = c("organ", "age"), features = 50)
plot_data_heatmap(mofa_trained, factor = 5, groups = "B1", scale="row", annotation_samples = c("organ", "age"), features = 50)

GSEA

# BiocManager::install("MOFAdata")
library(MOFAdata)
utils::data(reactomeGS)
head(rownames(reactomeGS))

## Remove row with NA
reactomeGS <- reactomeGS[!is.na(rownames(reactomeGS)),]
library(EnsDb.Hsapiens.v86)
hg.pairs <- readRDS(system.file("exdata", "human_cycle_markers.rds", package="scran"))
all_genes <- ensembldb::genes(EnsDb.Hsapiens.v86)
detach(package:EnsDb.Hsapiens.v86)
detach(package:ensembldb)

# gene_name_2_id <- function(gene){
#    return(all_genes[all_genes$gene_name==gene,]$gene_id[1])
# }
# 
# gene_ids <- sapply(mofa_trained@features_metadata$feature, gene_name_2_id)
# rowData(sce)["gene_id"] <- gene_ids
# rowData(sce)["gene_name"] <- rownames(sce)

gene_names_reactome <- all_genes[colnames(reactomeGS)]$gene_name
colnames(reactomeGS) <- gene_names_reactome

Subset to genes tested

reactomeGS_universe <- reactomeGS[, colnames(reactomeGS) %in% mofa_trained@features_metadata$feature]
# GSEA on positive weights, with default options
res.positive <- run_enrichment(mofa_trained,
  view='scaled_logcounts',
  # statistical.test = 'cor.adj.parametric',
  feature.sets = reactomeGS_universe, 
  sign = "positive",
)

# GSEA on negative weights, with default options
res.negative <- run_enrichment(mofa_trained, 
  view='scaled_logcounts',
  # statistical.test = 'cor.adj.parametric',
  feature.sets = reactomeGS_universe, 
  sign = "negative"
)


for (f in 1:mofa_trained@dimensions$K){
  if (min(res.positive$pval.adj[,paste0("Factor", f)]) < 0.1) {
    print(plot_enrichment(res.positive, factor = f, alpha=0.1) + ggtitle("Positive weights") +
            plot_enrichment(res.negative, factor = f, alpha=0.1) + ggtitle("Negative weights") +
              plot_annotation(title=paste0("Factor", f)))
      }
  }
signif_pathways <- rownames(data.frame(res.negative$pval.adj))[order(data.frame(res.negative$pval.adj)[["Factor8"]])[0:10]]
colnames(reactomeGS_universe)[reactomeGS_universe[signif_pathways[5],]==1]
plot_enrichment_detailed(res.negative, factor = 8)

Notes

  • Factor2 separates BM from rest
  • Factor5: immature VS mature B cell phenotype, separates mature B cells and B1 cells in liver and BM from the others, more mature phenotype (lower expr of VPREB1 and co.)

–> –> –> –> –> –> –> –> –> –> –> –> –> –> –> –> –>

–> –> –> –> –>

–> –> –>

LS0tCnRpdGxlOiAiRmFjdG9yIEFuYWx5c2lzIGZvciB3aXRoaW4tY2VsbHR5cGUgZGlmZmVyZW5jZXMgb24gb24gcGFuLWZldGFsIGltbXVuZSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgRW52IHNldHVwCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKHJlbnYpCnJlbnY6OmluaXQoKQpyZW52OjppbnN0YWxsKCJyZXRpY3VsYXRlIikKcmVudjo6dXNlX3B5dGhvbigpCgpweV9wa2dzIDwtIGMoCiAgICAic2NhbnB5IiwKICAgICJhbm5kYXRhIiwKICAgICJtb2ZhcHkyIgopCgpyZXRpY3VsYXRlOjpweV9pbnN0YWxsKHB5X3BrZ3MpCgpCaW9jTWFuYWdlcjo6aW5zdGFsbChjKCJTaW5nbGVDZWxsRXhwZXJpbWVudCIsICJzY3JhbiIsICJiYXRjaGVsb3IiLCAic2NhdGVyIiwgJ3RpZHl2ZXJzZScpKQppbnN0YWxsLnBhY2thZ2VzKGMoInBhdGNod29yayIpKQpgYGAKCmBgYHtyfQojIHNldHdkKCd+L1Bhbl9mZXRhbF9pbW11bmUvc3JjLzVfb3JnYW5fc2lnbmF0dXJlcy9NT0ZBX2ZhY3Rvcl9hbmFseXNpcy8nKQojIHJlbnY6OmFjdGl2YXRlKCkKIyByZW52Ojp1c2VfcHl0aG9uKCkKIyAKIyBweV9wa2dzIDwtIGMoCiMgICAgICJzY2FucHkiLAojICAgICAiYW5uZGF0YSIsCiMgICAgICJtb2ZhcHkyIgojICkKIyAKIyByZXRpY3VsYXRlOjpweV9pbnN0YWxsKHB5X3BrZ3MpCmBgYAoKCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKCJlbGxpcHNpcyIpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeSh0aWR5dmVyc2UpCiAgbGlicmFyeShNT0ZBMikKICBsaWJyYXJ5KE1hdHJpeCkKICBsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQogIGxpYnJhcnkoc2NyYW4pCiAgbGlicmFyeShnbHVlKQogIGxpYnJhcnkoc2NhdGVyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQogIGxpYnJhcnkoYmF0Y2hlbG9yKQogIGxpYnJhcnkocmhkZjUpCiAgIyBsaWJyYXJ5KGdncmFwaCkKICB9CiAgKQpgYGAKCkRlZmluZSBwbG90dGluZyB1dGlscwpgYGB7cn0KcmVtb3ZlX3hfYXhpcyA8LSBmdW5jdGlvbigpewogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkgIAp9CgpyZW1vdmVfeV9heGlzIDwtIGZ1bmN0aW9uKCl7CiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpKSAgCn0KCm9yZ19jb2xvcnMgPC0gcmVhZF9jc3YoIn4vUGFuX2ZldGFsX2ltbXVuZS9tZXRhZGF0YS9vcmdhbl9jb2xvcnMuY3N2IikKb3JnX2NvbG9ycyA8LSBzZXROYW1lcyhvcmdfY29sb3JzJGNvbG9yLCBvcmdfY29sb3JzJG9yZ2FuKQpgYGAKCmBgYHtyfQpmaWdkaXIgPC0gIn4vbW91bnQvZ2RyaXZlL1Bhbl9mZXRhbC9VcGRhdGVzX2FuZF9wcmVzZW50YXRpb25zL2ZpZ3VyZXMvTU9GQV9hbmFseXNpcy8iCmlmICghZGlyLmV4aXN0cyhmaWdkaXIpKXsgZGlyLmNyZWF0ZShmaWdkaXIpIH0KYGBgCgojIyBMb2FkIHBzZXVkb2J1bGtlZCBkYXRhCgpgYGB7cn0Kc3BsaXQgPSAiTFlNUEhPSUQiCmluZGlyIDwtIGdsdWUoIi9uZnMvdGVhbTIwNS9lZDYvZGF0YS9GZXRhbF9pbW11bmUvTE1NX2RhdGEvTE1NX2lucHV0X3tzcGxpdH1fUEJVTEsvIikKCm1hdHJpeCA8LSByZWFkTU0oZmlsZSA9IHBhc3RlMChpbmRpciwgIm1hdHJpeC5tdHguZ3oiKSkKY29sZGF0YSA8LSByZWFkLmNzdihmaWxlID0gcGFzdGUwKGluZGlyLCAibWV0YWRhdGEuY3N2Lmd6IikpICAlPiUKICBjb2x1bW5fdG9fcm93bmFtZXMoIlgiKQpyb3dkYXRhIDwtIHJlYWQuY3N2KGZpbGUgPSBwYXN0ZTAoaW5kaXIsICJnZW5lLmNzdi5neiIpKSAKCiMjIE1ha2UgU2luZ2xlQ2VsbEV4cGVyaW1lbnQgb2JqCnNjZSA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChsaXN0KGxvZ2NvdW50cyA9IHQobWF0cml4KSksIGNvbERhdGEgPSBjb2xkYXRhKQpyb3duYW1lcyhzY2UpIDwtIG1ha2UudW5pcXVlKHJvd2RhdGEkR2VuZU5hbWUpIApgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9CiMjIFBsb3QgbnVtYmVyIG9mIGNlbGxzIHBlciBvcmdhbi9jZWxsdHlwZSBwYWlyCm5fY2VsbHNfaGVhdG1hcCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZ3JvdXBfYnkoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgb3JnYW4pICU+JQogIHN1bW1hcmlzZShuX2NlbGxzPXN1bShuX2NlbGxzKSkgJT4lCiAgZ2dwbG90KGFlcyhhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikpICsKICBnZW9tX3RpbGUoYWVzKGZpbGw9bG9nMTAobl9jZWxscykpKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uX2NlbGxzKSwgY29sb3I9IndoaXRlIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpICsKICB4bGFiKCJjZWxsdHlwZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpCgpuX3NhbXBsZXNfaGVhdG1hcCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZ3JvdXBfYnkoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgb3JnYW4pICU+JQogIHN1bW1hcmlzZShuX3NhbXBsZXM9bigpKSAlPiUKICBnZ3Bsb3QoYWVzKGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSkgKwogIGdlb21fdGlsZShhZXMoZmlsbD1uX3NhbXBsZXMpKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uX3NhbXBsZXMpLCBjb2xvcj0id2hpdGUiKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uPSJjaXZpZGlzIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpICsKICB4bGFiKCJjZWxsdHlwZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KSkKCm5fY2VsbHNfaGVhdG1hcCAvIG5fc2FtcGxlc19oZWF0bWFwCmBgYAoKIyMgUHJlcHJvY2Vzc2luZwoKIyMjIEZpbHRlcmluZyBzYW1wbGVzCgpgYGB7cn0KIyMgRmlsdGVyIG91dCBzYW1wbGVzIHdpdGggbGVzcyB0aGFuIDIwIGNlbGxzCnNjZSA8LSBzY2VbLHNjZSRuX2NlbGxzID4gMjBdCgojIEV4Y2x1ZGUgY2VsbHR5cGVzIHByZXNlbnQgaW4ganVzdCBvbmUgb3JnYW4Ka2VlcF9jdCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZHBseXI6OnNlbGVjdChvcmdhbiwgYW5ub19sdmxfMl9maW5hbF9jbGVhbikgJT4lCiAgZGlzdGluY3QoKSAlPiUKICBncm91cF9ieShhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKSAlPiUKICBzdW1tYXJpc2Uobj1uKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBmaWx0ZXIobiA+IDEpICU+JQogIHB1bGwoYW5ub19sdmxfMl9maW5hbF9jbGVhbikKCnNjZSA8LSBzY2VbLHNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuICVpbiUga2VlcF9jdF0KCiMgRmlsdGVyIG91dCBjZWxsdHlwZXMgd2l0aCBsZXNzIHRoYW4gMTAgc2FtcGxlcwprZWVwX2N0IDwtIGRhdGEuZnJhbWUoY29sRGF0YShzY2UpKSAlPiUKICBncm91cF9ieShhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKSAlPiUKICBzdW1tYXJpc2Uobl9zYW1wbGVzPW4oKSkgJT4lCiAgZmlsdGVyKG5fc2FtcGxlcyA+PSAxMCkgJT4lCiAgcHVsbChhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKQoKc2NlIDwtIHNjZVssc2NlJGFubm9fbHZsXzJfZmluYWxfY2xlYW4gJWluJSBrZWVwX2N0XQoKIyMgRXhjbHVkZSBsb3cgcXVhbGl0eSBjbHVzdGVycwphbm5vX2dyb3VwcyA8LSBqc29ubGl0ZTo6ZnJvbUpTT04odHh0ID0gICJ+L1Bhbl9mZXRhbF9pbW11bmUvbWV0YWRhdGEvYW5ub19ncm91cHMuanNvbiIpCnNjZSA8LSBzY2VbLCFzY2UkYW5ub19sdmxfMl9maW5hbF9jbGVhbiAlaW4lIGFubm9fZ3JvdXBzJE9USEVSXQoKIyMgRXhjbHVkZSBkb25vciBGMTkgKGxvdyBRKQpzY2UgPC0gc2NlWywhc2NlJGRvbm9yICVpbiUgYygnRjE5JyldCmBgYAoKCmBgYHtyfQojIyBFeGNsdWRlIGRvbm9ycyBmb3Igd2hpY2ggd2UgaGF2ZSBvbmx5IG9uZSBvcmdhbgprZWVwLmRvbm9ycyA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSlbYygiU2FtcGxlIiwgImRvbm9yIiwgIm9yZ2FuIiwgJ2FnZScpXSAlPiUKICBkaXN0aW5jdChkb25vciwgb3JnYW4sIFNhbXBsZSwgYWdlKSAlPiUKICBncm91cF9ieShkb25vcikgJT4lCiAgbXV0YXRlKG5fb3JnYW5zID0gbGVuZ3RoKHVuaXF1ZShvcmdhbikpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZmlsdGVyKG5fb3JnYW5zID49IDMpICU+JQogIHB1bGwoZG9ub3IpICU+JQogIHVuaXF1ZSgpCgogICMgYXJyYW5nZShuX29yZ2FucykgJT4lCiAgIyBtdXRhdGUoZG9ub3I9ZmFjdG9yKGRvbm9yLCBsZXZlbHM9dW5pcXVlKGRvbm9yKSkpICU+JQogICMgZ2dwbG90KGFlcyhkb25vciwgb3JnYW4sIGNvbG9yPWFnZSkpICsKICAjIGdlb21faml0dGVyKHdpZHRoPTAuMDEpICsKICAjIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpCmBgYAoKYGBge3J9CnNjZSA8LSBzY2VbLHNjZSRkb25vciAlaW4lIGtlZXAuZG9ub3JzXQpgYGAKCgpgYGB7ciwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTEwfQojIyBQbG90IG51bWJlciBvZiBjZWxscyBwZXIgb3JnYW4vY2VsbHR5cGUgcGFpcgpuX2NlbGxzX2hlYXRtYXAgPC0gZGF0YS5mcmFtZShjb2xEYXRhKHNjZSkpICU+JQogIGdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSAlPiUKICBzdW1tYXJpc2Uobl9jZWxscz1zdW0obl9jZWxscykpICU+JQogIGdncGxvdChhZXMoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgb3JnYW4pKSArCiAgZ2VvbV90aWxlKGFlcyhmaWxsPWxvZzEwKG5fY2VsbHMpKSkgKwogIGdlb21fdGV4dChhZXMobGFiZWw9bl9jZWxscyksIGNvbG9yPSJ3aGl0ZSIpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE2KSArCiAgeGxhYigiY2VsbHR5cGUiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKQoKbl9zYW1wbGVzX2hlYXRtYXAgPC0gZGF0YS5mcmFtZShjb2xEYXRhKHNjZSkpICU+JQogIGdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSAlPiUKICBzdW1tYXJpc2Uobl9zYW1wbGVzPW4oKSkgJT4lCiAgZ2dwbG90KGFlcyhhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikpICsKICBnZW9tX3RpbGUoYWVzKGZpbGw9bl9zYW1wbGVzKSkgKwogIGdlb21fdGV4dChhZXMobGFiZWw9bl9zYW1wbGVzKSwgY29sb3I9IndoaXRlIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbj0iY2l2aWRpcyIpICsKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE2KSArCiAgeGxhYigiY2VsbHR5cGUiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTEsIHZqdXN0PTAuNSkpCgpuX2NlbGxzX2hlYXRtYXAgLyBuX3NhbXBsZXNfaGVhdG1hcApgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9Cm9yZ2FuX29yZGVyIDwtIGMoIllTIiwgIkxJIiwgIkJNIiwgIlRIIiwgIlNQIiwgIlNLIiwiS0kiLCJHVSIsICJNTE4iKQpwbCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSAlPiUKICBkcGx5cjo6c3VtbWFyaXNlKG5fY2VsbHM9c3VtKG5fY2VsbHMpLCBuX3NhbXBsZXM9ZHBseXI6Om4oKSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4pICU+JQogIGRwbHlyOjptdXRhdGUobl9vcmdhbnM9ZHBseXI6Om4oKSwgb3JnX2ZyYWM9bl9jZWxscy9zdW0obl9jZWxscykpICU+JQogICMjIGlzIHRoZSBjdCBvdmVycmVwcmVzZW50ZWQgaW4gb25lIG9yZ2FuPwogIGRwbHlyOjptdXRhdGUoZGVsdGFfbWF4X29yZ19mcmFjID0gbWF4KG9yZ19mcmFjKS1vcmdfZnJhYykgJT4lIAogIGRwbHlyOjptdXRhdGUobWVhbl9kZWx0YV9tYXhfb3JnX2ZyYWMgPSBtZWFuKGRlbHRhX21heF9vcmdfZnJhYykpICU+JSAKICBkcGx5cjo6dW5ncm91cCgpICU+JQogIGRwbHlyOjphcnJhbmdlKG5fb3JnYW5zLCAtbWVhbl9kZWx0YV9tYXhfb3JnX2ZyYWMpICU+JQogIGRwbHlyOjptdXRhdGUoYW5ub19sdmxfMl9maW5hbF9jbGVhbj1mYWN0b3IoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgbGV2ZWxzPXJldih1bmlxdWUoYW5ub19sdmxfMl9maW5hbF9jbGVhbikpKSkgJT4lCiAgZHBseXI6Om11dGF0ZShvcmdhbj1mYWN0b3Iob3JnYW4sIGxldmVscz1yZXYob3JnYW5fb3JkZXIpKSkgJT4lCiAgZ2dwbG90KGFlcyhhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvcj1sb2cxMChuX2NlbGxzKSwgc2l6ZT1uX3NhbXBsZXMpKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uX2NlbGxzKSwgY29sb3I9IndoaXRlIikgKwogIHNjYWxlX3NpemUocmFuZ2U9Yyg3LDE4KSwgYnJlYWtzID0gYygwLDEsMTAsMzApLCBuYW1lPSIjIHNhbXBsZXMiKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MoKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKG5hbWU9ImxvZzEwKCMgY2VsbHMpIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMjApICsKICB4bGFiKCJjZWxsdHlwZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KSkgCgojIyBTYXZlIG9yZGVyIG9mIENUcyBmcm9tIHdpZGVzcHJlYWQgdG8gcmVzdHJpY3RlZAphbm5vX29yZGVyIDwtIGxldmVscyhwbCRkYXRhJGFubm9fbHZsXzJfZmluYWxfY2xlYW4pCgpnZ3NhdmUocGFzdGUwKGZpZ2RpciwgImN0X29yZ2FuX2Rpc3RyaWJ1dGlvbi5wZGYiKSwgcGwsIHdpZHRoID0gMTUsIGhlaWdodCA9IDEwKQpgYGAKCiMjIyBUZWNobmljYWwgZWZmZWN0IGNvcnJlY3Rpb24gCgpgYGB7cn0KIyMgRmVhdHVyZSBzZWxlY3Rpb24gdyBzY3JhbiBXSVRISU4gQ0VMTFRZUEUKYW5ub19ncm91cHMgPC0gc3BsaXQoY29sbmFtZXMoc2NlKSwgc2NlJGFubm9fbHZsXzJfZmluYWxfY2xlYW4pCmFsbF9odmdzIDwtIGMoKQpmb3IgKGkgaW4gYW5ub19ncm91cHMpewogIGRlYyA8LSBtb2RlbEdlbmVWYXIoc2NlWyxpXSkKICBodmdzIDwtIGdldFRvcEhWR3MoZGVjLCBuID0gMTAwMCkKICBhbGxfaHZncyA8LSB1bmlvbihhbGxfaHZncywgaHZncykKICB9CgpzY2UgPC0gc2NlW3doaWNoKHJvd1N1bXMobG9nY291bnRzKHNjZSkpID4gMCksXQpzY2UKYGBgCgpFREEgd2l0aCBQQ0EKYGBge3IsIGZpZy5oZWlnaHQ9MTUsIGZpZy53aWR0aD0xNX0Kc2NlIDwtIHJ1blBDQShzY2UsIHNjYWxlPVRSVUUsIG5jb21wb25lbnRzPTMwLCAKICAgICAgICAgICAgICBleHByc192YWx1ZXMgPSAibG9nY291bnRzIiwgc3Vic2V0X3Jvdz1hbGxfaHZncykKcGxvdFBDQShzY2UsIGNvbG91cl9ieT0iZG9ub3IiLCBuY29tcG9uZW50cz02KQpwbG90UENBKHNjZSwgY29sb3VyX2J5PSJtZXRob2QiLCBuY29tcG9uZW50cz02KQpwbG90UENBKHNjZSwgY29sb3VyX2J5PSJvcmdhbiIsIG5jb21wb25lbnRzPTEwKQpgYGAKCk1pbmltaXplIG9idmlvdXMgdGVjaG5pY2FsIGVmZmVjdHMgKDNHRVgvNUdFWCwgZG9ub3IpIHVzaW5nIGxpbmVhciByZWdyZXNzaW9uIChmb2xsb3dpbmcgcHJvY2VkdXJlIGZyb20gW09TQ0FdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy9yZWxlYXNlL09TQ0EvaW50ZWdyYXRpbmctZGF0YXNldHMuaHRtbCNsaW5lYXItcmVncmVzc2lvbikpCgpgYGB7cn0KIyMgUmVncmVzcyB0ZWNobmljYWwgZWZmZWN0cwpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH5kb25vcittZXRob2QsZGF0YT1jb2xEYXRhKHNjZSkpCnJlc2lkdWFscyA8LSByZWdyZXNzQmF0Y2hlcyhzY2UsIGFzc2F5LnR5cGUgPSAibG9nY291bnRzIiwgZGVzaWduID0gZGVzaWduKQphc3NheShzY2UsICJjb3JyZWN0ZWRfbG9nY291bnRzIikgPC0gYXMubWF0cml4KGFzc2F5KHJlc2lkdWFsc1ssY29sbmFtZXMoc2NlKV0sICJjb3JyZWN0ZWQiKSkKCiMjIFJlZ3Jlc3Mgb3JnYW4gKHNvdXAgZWZmZWN0KQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH5vcmdhbixkYXRhPWNvbERhdGEoc2NlKSkgIyMgSW5jbHVkZSBvcmdhbiB0ZXJtIHRvIGNhcHR1cmUgc291cApyZXNpZHVhbHMgPC0gcmVncmVzc0JhdGNoZXMoc2NlLCBhc3NheS50eXBlID0gImNvcnJlY3RlZF9sb2djb3VudHMiLCBkZXNpZ24gPSBkZXNpZ24pCmFzc2F5KHNjZSwgImNvcnJlY3RlZF9sb2djb3VudHMiKSA8LSBhcy5tYXRyaXgoYXNzYXkocmVzaWR1YWxzWyxjb2xuYW1lcyhzY2UpXSwgImNvcnJlY3RlZCIpKQoKYGBgCgpDaGVjayByZWdyZXNzaW9uIGhhcyBhbiBlZmZlY3QgcmVwZWF0aW5nIFBDQQpgYGB7ciwgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTE1fQpzY2UgPC0gcnVuUENBKHNjZSwgc2NhbGU9VFJVRSwgbmNvbXBvbmVudHM9MzAsIGV4cHJzX3ZhbHVlcyA9ICJjb3JyZWN0ZWRfbG9nY291bnRzIikKCnBsb3RQQ0Eoc2NlLCBjb2xvdXJfYnk9Im1ldGhvZCIsIG5jb21wb25lbnRzPTYpCnBsb3RQQ0Eoc2NlLCBjb2xvdXJfYnk9ImRvbm9yIiwgbmNvbXBvbmVudHM9NikKcGxvdFBDQShzY2UsIGNvbG91cl9ieT0ib3JnYW4iLCBuY29tcG9uZW50cz04KQpgYGAKCiMjIyBGZWF0dXJlIHNlbGVjdGlvbgoKYGBge3J9CiMjIEZlYXR1cmUgc2VsZWN0aW9uIHcgc2NyYW4gV0lUSElOIENFTExUWVBFCmFubm9fZ3JvdXBzIDwtIHNwbGl0KGNvbG5hbWVzKHNjZSksIHNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKQphbGxfaHZncyA8LSBjKCkKZm9yIChpIGluIGFubm9fZ3JvdXBzKXsKICBkZWMgPC0gbW9kZWxHZW5lVmFyKHNjZVssaV0sIGFzc2F5LnR5cGUgPSAiY29ycmVjdGVkX2xvZ2NvdW50cyIpCiAgaHZncyA8LSBnZXRUb3BIVkdzKGRlYywgbiA9IDEwMDApCiAgYWxsX2h2Z3MgPC0gdW5pb24oYWxsX2h2Z3MsIGh2Z3MpCiAgfQpgYGAKCjwhLS0gYGBge3J9IC0tPgo8IS0tIGRhdGEuZnJhbWUoY29sRGF0YShzY2UpKSAlPiUgLS0+CjwhLS0gICBncm91cF9ieShhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikgJT4lIC0tPgo8IS0tICAgc3VtbWFyaXNlKG5fc2FtcGxlcz1uKCksIG5fY2VsbHM9c3VtKG5fY2VsbHMpKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKG5fc2FtcGxlcywgbG9nMTAobl9jZWxscykpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChzaXplPTAuOCwgYWxwaGE9MC42KSAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTB9IC0tPgo8IS0tIHAgPC0gZGF0YS5mcmFtZShyZWR1Y2VkRGltKHNjZSlbLDE6Ml0pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShvcmdhbj1zY2Ukb3JnYW4sIGNlbGx0eXBlPXNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoY29sb3I9aWZlbHNlKGNlbGx0eXBlPT0iTUFUVVJFIEIgQ0VMTCIsICJFTFAiLCBOQSkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoUEMxLCBQQzIpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChjb2xvcj0iZ3JleSIpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBzaXplPTIpICsgLS0+CjwhLS0gICBnZW9tX3J1ZyhkYXRhPS4gJT4lIGZpbHRlcighaXMubmEoY29sb3IpKSwgYWVzKGNvbG9yPW9yZ2FuKSwgYWxwaGE9MC41KSAtLT4KCjwhLS0gcCAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBsaWJyYXJ5KFJDb2xvckJyZXdlcikgLS0+CjwhLS0gb3JnX2NvbG9ycyA8LSBzZXROYW1lcyhicmV3ZXIucGFsKDksICJTZXQxIiksIHVuaXF1ZShzY2Ukb3JnYW4pKSAtLT4KPCEtLSBgYGAgLS0+CgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gc2NlX21hdHVyZUIgPC0gc2NlWyxzY2UkYW5ub19sdmxfMl9maW5hbF9jbGVhbj09Ik1BVFVSRSBCIENFTEwiXSAtLT4KPCEtLSBhc3NheShzY2VfbWF0dXJlQiwgInNjYWxlZF9sb2djb3VudHMiKSA8LSB0KHNjYWxlKHQobG9nY291bnRzKHNjZV9tYXR1cmVCKSkpKSAtLT4KCgo8IS0tIHNjZV9tYXR1cmVCIDwtIHJ1blBDQShzY2VfbWF0dXJlQiwgc2NhbGU9RkFMU0UsIG5jb21wb25lbnRzPTMwLCBleHByc192YWx1ZXMgPSAic2NhbGVkX2xvZ2NvdW50cyIpIC0tPgoKPCEtLSBkYXRhLmZyYW1lKHJlZHVjZWREaW0oc2NlX21hdHVyZUIpWywyOjNdKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUob3JnYW49c2NlX21hdHVyZUIkb3JnYW4sIGNlbGx0eXBlPXNjZV9tYXR1cmVCJGFubm9fbHZsXzJfZmluYWxfY2xlYW4pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShjb2xvcj1pZmVsc2Uob3JnYW4gJWluJSBjKCJUSCIsIkJNIiksICJFTFAiLCBOQSkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoUEMyLCBQQzMpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChjb2xvcj0iZ3JleSIpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBzaXplPTIpICsgLS0+CjwhLS0gICBnZW9tX3J1ZyhkYXRhPS4gJT4lIGZpbHRlcighaXMubmEoY29sb3IpKSwgYWVzKGNvbG9yPW9yZ2FuKSwgYWxwaGE9MC41KSArIC0tPgo8IS0tICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1vcmdfY29sb3JzKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplPTE2KSArIC0tPgo8IS0tICAgZ2d0aXRsZSgiTUFUVVJFIEIgQ0VMTCIpIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gc2NlX21hdHVyZUIgPC0gc2NlWyxzY2UkYW5ub19sdmxfMl9maW5hbF9jbGVhbj09Ik5LIl0gLS0+CjwhLS0gYXNzYXkoc2NlX21hdHVyZUIsICJzY2FsZWRfbG9nY291bnRzIikgPC0gdChzY2FsZSh0KGxvZ2NvdW50cyhzY2VfbWF0dXJlQikpKSkgLS0+Cgo8IS0tIHNjZV9tYXR1cmVCIDwtIHJ1blBDQShzY2VfbWF0dXJlQiwgc2NhbGU9RkFMU0UsIG5jb21wb25lbnRzPTMwLCBleHByc192YWx1ZXMgPSAic2NhbGVkX2xvZ2NvdW50cyIpIC0tPgoKPCEtLSAjIyBWYXJpYW5jZSBleHBsYWluZWQgLS0+CjwhLS0gZGF0YS5mcmFtZShyZWR1Y2VkRGltKHNjZV9tYXR1cmVCKVssMTo0XSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKG9yZ2FuPXNjZV9tYXR1cmVCJG9yZ2FuLCAgLS0+CjwhLS0gICAgICAgICAgY2VsbHR5cGU9c2NlX21hdHVyZUIkYW5ub19sdmxfMl9maW5hbF9jbGVhbikgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGNvbG9yPWlmZWxzZShvcmdhbiAlaW4lIGMoIkdVIiwgIlNQIiksICJFTFAiLCBOQSkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoUEMxLCBQQzMpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChjb2xvcj0iZ3JleSIpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBzaXplPTIpICsgLS0+CjwhLS0gICBnZW9tX3J1ZyhkYXRhPS4gJT4lIGZpbHRlcighaXMubmEoY29sb3IpKSwgYWVzKGNvbG9yPW9yZ2FuKSwgYWxwaGE9MC41KSArIC0tPgo8IS0tICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1vcmdfY29sb3JzKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplPTE2KSArIC0tPgo8IS0tICAgZ2d0aXRsZSgiTksgQ0VMTCIpIC0tPgo8IS0tIGBgYCAtLT4KCgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBkYXRhLmZyYW1lKHJlZHVjZWREaW0oc2NlX21hdHVyZUIpWywyOjNdKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUob3JnYW49c2NlX21hdHVyZUIkb3JnYW4sIGNlbGx0eXBlPXNjZV9tYXR1cmVCJGFubm9fbHZsXzJfZmluYWxfY2xlYW4pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShjb2xvcj1pZmVsc2UoY2VsbHR5cGU9PSJNQVRVUkUgQiBDRUxMIiwgIkVMUCIsIE5BKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhQQzIsIFBDMykpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGNvbG9yPSJncmV5IikgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoIWlzLm5hKGNvbG9yKSksIGFlcyhjb2xvcj1vcmdhbiksIHNpemU9MikgKyAtLT4KPCEtLSAgIGdlb21fcnVnKGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBhbHBoYT0wLjUpICsgLS0+CjwhLS0gICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU3BlY3RyYWwiKSAtLT4KPCEtLSBgYGAgLS0+CgoKIyBGQSBNb2RlbCAtIE5vcm1hbCBNT0ZBIC8gb25seSBjZWxsdHlwZXMgYXMgZ3JvdXBzCgpNYWtlIE1PRkEgb2JqZWN0IChVc2UgY2VsbHR5cGVzIGFzIGdyb3VwaW5nIGNvdmFyaWF0ZSkKCmBgYHtyfQptb2ZhIDwtIGNyZWF0ZV9tb2ZhX2Zyb21fU2luZ2xlQ2VsbEV4cGVyaW1lbnQoc2NlW2FsbF9odmdzLF0sIGFzc2F5ID0gImNvcnJlY3RlZF9sb2djb3VudHMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwcyA9ICJhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuIiwgZXh0cmFjdF9tZXRhZGF0YSA9IFRSVUUpCgpzYXZlUkRTKG1vZmEsIGdsdWUoJ3tpbmRpcn1MWU1QSE9JRF9tb2ZhX29ial9vcmdhbkNvcnJlY3RlZF9maWx0ZXJlZERvbm9ycy5SRFMnKSkKbW9mYV9vYmogPC0gcmVhZFJEUyhnbHVlKCd7aW5kaXJ9TFlNUEhPSURfbW9mYV9vYmpfb3JnYW5Db3JyZWN0ZWRfZmlsdGVyZWREb25vcnMuUkRTJykpCmBgYAoKYGBge3J9Cm9iamVjdCA8LSBtb2ZhX29iagpgYGAKCgpQcmVwYXJlIDQgdHJhaW5pbmcKCmBgYHtyfQoKZGF0YV9vcHRzIDwtIGdldF9kZWZhdWx0X2RhdGFfb3B0aW9ucyhvYmplY3QpCmRhdGFfb3B0cyR1c2VfZmxvYXQzMiA8LSBUUlVFCmRhdGFfb3B0cyRjZW50ZXJfZ3JvdXBzIDwtIEZBTFNFCm9iamVjdEBkYXRhX29wdGlvbnMgPC0gZGF0YV9vcHRzCgptb2RlbF9vcHRzIDwtIGdldF9kZWZhdWx0X21vZGVsX29wdGlvbnMob2JqZWN0KQptb2RlbF9vcHRzJG51bV9mYWN0b3JzIDwtIDMwCiMgbW9kZWxfb3B0cyRhcmRfZmFjdG9ycyA8LSBGQUxTRQoKdHJhaW5fb3B0cyA8LSBnZXRfZGVmYXVsdF90cmFpbmluZ19vcHRpb25zKG9iamVjdCkKdHJhaW5fb3B0cyRzZWVkIDwtIDIwMjAKdHJhaW5fb3B0cyRjb252ZXJnZW5jZV9tb2RlIDwtICJtZWRpdW0iICMgdXNlICJmYXN0IiBmb3IgZmFzdGVyIHRyYWluaW5nCnRyYWluX29wdHMkc3RvY2hhc3RpYyA8LSBGQUxTRQoKIyBtZWZpc3RvX29wdHMgPC0gZ2V0X2RlZmF1bHRfbWVmaXN0b19vcHRpb25zKG9iamVjdCkKIyBtZWZpc3RvX29wdHMkd2FycGluZyA8LSBGQUxTRQojIG1lZmlzdG9fb3B0cyRzcGFyc2VHUCA8LSBUUlVFCgpvYmplY3QgPC0gcHJlcGFyZV9tb2ZhKAogIG9iamVjdCA9IG9iamVjdCwKICBkYXRhX29wdGlvbnMgPSBkYXRhX29wdHMsCiAgbW9kZWxfb3B0aW9ucyA9IG1vZGVsX29wdHMsCiAgdHJhaW5pbmdfb3B0aW9ucyA9IHRyYWluX29wdHMKKSAKCm9iamVjdApgYGAKCiMjIFRyYWluCgpXcmFwcGVkIGluIGBydW5fbW9mYS5SYAoKCmBgYHtyfQpvdXRmaWxlIDwtIGdsdWUoJ3tpbmRpcn17c3BsaXR9X21vZmFfbW9kZWxfb25ldmlld19vcmdhbkNvcnJlY3RlZF9maWx0ZXJlZERvbm9ycy5oZGY1JykKbW9mYV90cmFpbmVkIDwtIHJ1bl9tb2ZhKG9iamVjdCwgb3V0ZmlsZSA9IG91dGZpbGUpCmBgYAoKYGBge3J9CiMjIyBUd2Vha2luZyB0aGUgTU9GQTIgbG9hZGluZyBmdW5jdGlvbiBiZWNhdXNlIHRoZSBxdWFsaXR5IGNvbnRyb2wgY29tcGxhaW5zCmxvYWRfbW9kZWwgPC0gZnVuY3Rpb24oZmlsZSwgc29ydF9mYWN0b3JzID0gVFJVRSwgb25fZGlzayA9IEZBTFNFLCBsb2FkX2RhdGEgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgIHJlbW92ZV9vdXRsaWVycyA9IEZBTFNFLCByZW1vdmVfaW5hY3RpdmVfZmFjdG9ycyA9IFRSVUUsIHZlcmJvc2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICBsb2FkX2ludGVycG9sX1ogPSBGQUxTRSkgewoKICAjIENyZWF0ZSBuZXcgTU9GQW9kZWwgb2JqZWN0CiAgb2JqZWN0IDwtIG5ldygiTU9GQSIpCiAgb2JqZWN0QHN0YXR1cyA8LSAidHJhaW5lZCIKICAKICAjIFNldCBvbl9kaXNrIG9wdGlvbgogIGlmIChvbl9kaXNrKSB7IAogICAgb2JqZWN0QG9uX2Rpc2sgPC0gVFJVRSAKICB9IGVsc2UgeyAKICAgICAgb2JqZWN0QG9uX2Rpc2sgPC0gRkFMU0UgCiAgfQogIAogICMgR2V0IGdyb3VwcyBhbmQgZGF0YSBzZXQgbmFtZXMgZnJvbSB0aGUgaGRmNSBmaWxlIG9iamVjdAogIGg1bHMub3V0IDwtIGg1bHMoZmlsZSwgZGF0YXNldGluZm8gPSBGQUxTRSkKICAKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIyBMb2FkIHRyYWluaW5nIGRhdGEgIyMKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKCiAgIyBMb2FkIG5hbWVzCiAgaWYgKCJ2aWV3cyIgJWluJSBoNWxzLm91dCRuYW1lKSB7CiAgICB2aWV3X25hbWVzIDwtIGFzLmNoYXJhY3RlciggaDVyZWFkKGZpbGUsICJ2aWV3cyIpW1sxXV0gKQogICAgZ3JvdXBfbmFtZXMgPC0gYXMuY2hhcmFjdGVyKCBoNXJlYWQoZmlsZSwgImdyb3VwcyIpW1sxXV0gKQogICAgZmVhdHVyZV9uYW1lcyA8LSBoNXJlYWQoZmlsZSwgImZlYXR1cmVzIilbdmlld19uYW1lc10KICAgIHNhbXBsZV9uYW1lcyAgPC0gaDVyZWFkKGZpbGUsICJzYW1wbGVzIilbZ3JvdXBfbmFtZXNdIAogIH0gZWxzZSB7ICAjIGZvciBvbGQgbW9kZWxzCiAgICBmZWF0dXJlX25hbWVzIDwtIGg1cmVhZChmaWxlLCAiZmVhdHVyZXMiKQogICAgc2FtcGxlX25hbWVzICA8LSBoNXJlYWQoZmlsZSwgInNhbXBsZXMiKQogICAgdmlld19uYW1lcyA8LSBuYW1lcyhmZWF0dXJlX25hbWVzKQogICAgZ3JvdXBfbmFtZXMgPC0gbmFtZXMoc2FtcGxlX25hbWVzKQogICAgaDVscy5vdXQgPC0gaDVscy5vdXRbZ3JlcCgidmFyaWFuY2VfZXhwbGFpbmVkIiwgaDVscy5vdXQkbmFtZSwgaW52ZXJ0ID0gVFJVRSksXQogIH0KICBpZigiY292YXJpYXRlcyIgJWluJSAgaDVscy5vdXQkbmFtZSl7CiAgICBjb3ZhcmlhdGVfbmFtZXMgPC0gYXMuY2hhcmFjdGVyKCBoNXJlYWQoZmlsZSwgImNvdmFyaWF0ZXMiKVtbMV1dKQogIH0gZWxzZSB7CiAgICBjb3ZhcmlhdGVfbmFtZXMgPC0gTlVMTAogIH0KCiAgIyBMb2FkIHRyYWluaW5nIGRhdGEgKGFzIG5lc3RlZCBsaXN0IG9mIG1hdHJpY2VzKQogIGRhdGEgPC0gbGlzdCgpOyBpbnRlcmNlcHRzIDwtIGxpc3QoKQogIGlmIChsb2FkX2RhdGEgJiYgImRhdGEiJWluJWg1bHMub3V0JG5hbWUpIHsKICAgIAogICAgb2JqZWN0QGRhdGFfb3B0aW9uc1tbImxvYWRlZCJdXSA8LSBUUlVFCiAgICBpZiAodmVyYm9zZSkgbWVzc2FnZSgiTG9hZGluZyBkYXRhLi4uIikKICAgIAogICAgZm9yIChtIGluIHZpZXdfbmFtZXMpIHsKICAgICAgZGF0YVtbbV1dIDwtIGxpc3QoKQogICAgICBpbnRlcmNlcHRzW1ttXV0gPC0gbGlzdCgpCiAgICAgIGZvciAoZyBpbiBncm91cF9uYW1lcykgewogICAgICAgIGlmIChvbl9kaXNrKSB7CiAgICAgICAgICAjIGFzIERlbGF5ZWRBcnJheXMKICAgICAgICAgIGRhdGFbW21dXVtbZ11dIDwtIERlbGF5ZWRBcnJheTo6RGVsYXllZEFycmF5KCBIREY1QXJyYXlTZWVkKGZpbGUsIG5hbWUgPSBzcHJpbnRmKCJkYXRhLyVzLyVzIiwgbSwgZykgKSApCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICMgYXMgbWF0cmljZXMKICAgICAgICAgIGRhdGFbW21dXVtbZ11dIDwtIGg1cmVhZChmaWxlLCBzcHJpbnRmKCJkYXRhLyVzLyVzIiwgbSwgZykgKQogICAgICAgICAgdHJ5Q2F0Y2goaW50ZXJjZXB0c1tbbV1dW1tnXV0gPC0gYXMubnVtZXJpYyggaDVyZWFkKGZpbGUsIHNwcmludGYoImludGVyY2VwdHMvJXMvJXMiLCBtLCBnKSApICksIGVycm9yID0gZnVuY3Rpb24oZSkgeyBOVUxMIH0pCiAgICAgICAgfQogICAgICAgICMgUmVwbGFjZSBOYU4gYnkgTkEKICAgICAgICBkYXRhW1ttXV1bW2ddXVtpcy5uYW4oZGF0YVtbbV1dW1tnXV0pXSA8LSBOQSAjIHRoaXMgcmVhbGlzZWQgaW50byBtZW1vcnksIFRPIEZJWAogICAgICB9CiAgICB9CiAgICAKICAjIENyZWF0ZSBlbXB0eSB0cmFpbmluZyBkYXRhIChhcyBuZXN0ZWQgbGlzdCBvZiBlbXB0eSBtYXRyaWNlcywgd2l0aCB0aGUgY29ycmVjdCBkaW1lbnNpb25zKQogIH0gZWxzZSB7CiAgICAKICAgIG9iamVjdEBkYXRhX29wdGlvbnNbWyJsb2FkZWQiXV0gPC0gRkFMU0UKICAgIAogICAgZm9yIChtIGluIHZpZXdfbmFtZXMpIHsKICAgICAgZGF0YVtbbV1dIDwtIGxpc3QoKQogICAgICBmb3IgKGcgaW4gZ3JvdXBfbmFtZXMpIHsKICAgICAgICBkYXRhW1ttXV1bW2ddXSA8LSAuY3JlYXRlX21hdHJpeF9wbGFjZWhvbGRlcihyb3duYW1lcyA9IGZlYXR1cmVfbmFtZXNbW21dXSwgY29sbmFtZXMgPSBzYW1wbGVfbmFtZXNbW2ddXSkKICAgICAgfQogICAgfQogIH0KCiAgb2JqZWN0QGRhdGEgPC0gZGF0YQogIG9iamVjdEBpbnRlcmNlcHRzIDwtIGludGVyY2VwdHMKCgogICMgTG9hZCBtZXRhZGF0YSBpZiBhbnkKICBpZiAoInNhbXBsZXNfbWV0YWRhdGEiICVpbiUgaDVscy5vdXQkbmFtZSkgewogICAgb2JqZWN0QHNhbXBsZXNfbWV0YWRhdGEgPC0gYmluZF9yb3dzKGxhcHBseShncm91cF9uYW1lcywgZnVuY3Rpb24oZykgYXMuZGF0YS5mcmFtZShoNXJlYWQoZmlsZSwgc3ByaW50Zigic2FtcGxlc19tZXRhZGF0YS8lcyIsIGcpKSkpKQogIH0KICBpZiAoImZlYXR1cmVzX21ldGFkYXRhIiAlaW4lIGg1bHMub3V0JG5hbWUpIHsKICAgIG9iamVjdEBmZWF0dXJlc19tZXRhZGF0YSA8LSBiaW5kX3Jvd3MobGFwcGx5KHZpZXdfbmFtZXMsIGZ1bmN0aW9uKG0pIGFzLmRhdGEuZnJhbWUoaDVyZWFkKGZpbGUsIHNwcmludGYoImZlYXR1cmVzX21ldGFkYXRhLyVzIiwgbSkpKSkpCiAgfQogIAogICMgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgIyMgTG9hZCBzYW1wbGUgY292YXJpYXRlcyAjIwogICMgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgCiAgIyBpZiAoYW55KGdyZXBsKCJjb3Zfc2FtcGxlcyIsIGg1bHMub3V0JGdyb3VwKSkpewogICMgICBjb3ZhcmlhdGVzIDwtIGxpc3QoKQogICMgICBmb3IgKGcgaW4gZ3JvdXBfbmFtZXMpIHsKICAjICAgICBpZiAob25fZGlzaykgewogICMgICAgICAgIyBhcyBEZWxheWVkQXJyYXlzCiAgIyAgICAgICBjb3ZhcmlhdGVzW1tnXV0gPC0gRGVsYXllZEFycmF5OjpEZWxheWVkQXJyYXkoIEhERjVBcnJheVNlZWQoZmlsZSwgbmFtZSA9IHNwcmludGYoImNvdl9zYW1wbGVzLyVzIiwgZykgKSApCiAgIyAgICAgfSBlbHNlIHsKICAjICAgICAgICMgYXMgbWF0cmljZXMKICAjICAgICAgIGNvdmFyaWF0ZXNbW2ddXSA8LSBoNXJlYWQoZmlsZSwgc3ByaW50ZigiY292X3NhbXBsZXMvJXMiLCBnKSApCiAgIyAgICAgfSAgICAKICAjICAgfQogICMgfSBlbHNlIGNvdmFyaWF0ZXMgPC0gTlVMTAogICMgb2JqZWN0QGNvdmFyaWF0ZXMgPC0gY292YXJpYXRlcwoKICAjIGlmIChhbnkoZ3JlcGwoImNvdl9zYW1wbGVzX3RyYW5zZm9ybWVkIiwgaDVscy5vdXQkZ3JvdXApKSl7CiAgIyAgIGNvdmFyaWF0ZXNfd2FycGVkIDwtIGxpc3QoKQogICMgICBmb3IgKGcgaW4gZ3JvdXBfbmFtZXMpIHsKICAjICAgICBpZiAob25fZGlzaykgewogICMgICAgICAgIyBhcyBEZWxheWVkQXJyYXlzCiAgIyAgICAgICBjb3ZhcmlhdGVzX3dhcnBlZFtbZ11dIDwtIERlbGF5ZWRBcnJheTo6RGVsYXllZEFycmF5KCBIREY1QXJyYXlTZWVkKGZpbGUsIG5hbWUgPSBzcHJpbnRmKCJjb3Zfc2FtcGxlc190cmFuc2Zvcm1lZC8lcyIsIGcpICkgKQogICMgICAgIH0gZWxzZSB7CiAgIyAgICAgICAjIGFzIG1hdHJpY2VzCiAgIyAgICAgICBjb3ZhcmlhdGVzX3dhcnBlZFtbZ11dIDwtIGg1cmVhZChmaWxlLCBzcHJpbnRmKCJjb3Zfc2FtcGxlc190cmFuc2Zvcm1lZC8lcyIsIGcpICkKICAjICAgICB9ICAgIAogICMgICB9CiAgIyB9IGVsc2UgY292YXJpYXRlc193YXJwZWQgPC0gTlVMTAogICMgb2JqZWN0QGNvdmFyaWF0ZXNfd2FycGVkIDwtIGNvdmFyaWF0ZXNfd2FycGVkCiAgCiAgIyAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgIyMgTG9hZCBpbnRlcnBvbGF0ZWQgZmFjdG9yIHZhbHVlcyAjIwogICMgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIAogICMgaW50ZXJwb2xhdGVkX1ogPC0gbGlzdCgpCiAgIyBpZiAoaXNUUlVFKGxvYWRfaW50ZXJwb2xfWikpIHsKICAjICAgCiAgIyAgIGlmIChpc1RSVUUodmVyYm9zZSkpIG1lc3NhZ2UoIkxvYWRpbmcgaW50ZXJwb2xhdGVkIGZhY3RvciB2YWx1ZXMuLi4iKQogICMgICAKICAjICAgZm9yIChnIGluIGdyb3VwX25hbWVzKSB7CiAgIyAgICAgaW50ZXJwb2xhdGVkX1pbW2ddXSA8LSBsaXN0KCkKICAjICAgICBpZiAob25fZGlzaykgewogICMgICAgICAgIyBhcyBEZWxheWVkQXJyYXlzCiAgIyAgICAgICAjIGludGVycG9sYXRlZF9aW1tnXV0gPC0gRGVsYXllZEFycmF5OjpEZWxheWVkQXJyYXkoIEhERjVBcnJheVNlZWQoZmlsZSwgbmFtZSA9IHNwcmludGYoIlpfcHJlZGljdGlvbnMvJXMiLCBnKSApICkKICAjICAgICB9IGVsc2UgewogICMgICAgICAgIyBhcyBtYXRyaWNlcwogICMgICAgICAgdHJ5Q2F0Y2goIHsKICAjICAgICAgICAgaW50ZXJwb2xhdGVkX1pbW2ddXVtbIm1lYW4iXV0gPC0gaDVyZWFkKGZpbGUsIHNwcmludGYoIlpfcHJlZGljdGlvbnMvJXMvbWVhbiIsIGcpICkKICAjICAgICAgIH0sIGVycm9yID0gZnVuY3Rpb24oeCkgeyBwcmludCgiUHJlZGljaXRpb25zIG9mIFogbm90IGZvdW5kLCBub3QgbG9hZGluZyBpdC4uLiIpIH0pCiAgIyAgICAgICB0cnlDYXRjaCggewogICMgICAgICAgICBpbnRlcnBvbGF0ZWRfWltbZ11dW1sidmFyaWFuY2UiXV0gPC0gaDVyZWFkKGZpbGUsIHNwcmludGYoIlpfcHJlZGljdGlvbnMvJXMvdmFyaWFuY2UiLCBnKSApCiAgIyAgICAgICB9LCBlcnJvciA9IGZ1bmN0aW9uKHgpIHsgcHJpbnQoIlZhcmlhbmNlIG9mIHByZWRpY3Rpb25zIG9mIFogbm90IGZvdW5kLCBub3QgbG9hZGluZyBpdC4uLiIpIH0pCiAgIyAgICAgICB0cnlDYXRjaCggewogICMgICAgICAgICBpbnRlcnBvbGF0ZWRfWltbZ11dW1sibmV3X3ZhbHVlcyJdXSA8LSBoNXJlYWQoZmlsZSwgIlpfcHJlZGljdGlvbnMvbmV3X3ZhbHVlcyIpCiAgIyAgICAgICB9LCBlcnJvciA9IGZ1bmN0aW9uKHgpIHsgcHJpbnQoIk5ldyB2YWx1ZXMgb2YgWiBub3QgZm91bmQsIG5vdCBsb2FkaW5nIGl0Li4uIikgfSkKICAjICAgICB9CiAgIyAgIH0KICAjIH0KICAjIG9iamVjdEBpbnRlcnBvbGF0ZWRfWiA8LSBpbnRlcnBvbGF0ZWRfWgogIAogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyMgTG9hZCBleHBlY3RhdGlvbnMgIyMKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKICBleHBlY3RhdGlvbnMgPC0gbGlzdCgpCiAgbm9kZV9uYW1lcyA8LSBoNWxzLm91dFtoNWxzLm91dCRncm91cD09Ii9leHBlY3RhdGlvbnMiLCJuYW1lIl0KCiAgaWYgKHZlcmJvc2UpIG1lc3NhZ2UocGFzdGUwKCJMb2FkaW5nIGV4cGVjdGF0aW9ucyBmb3IgIiwgbGVuZ3RoKG5vZGVfbmFtZXMpLCAiIG5vZGVzLi4uIikpCgogIGlmICgiQWxwaGFXIiAlaW4lIG5vZGVfbmFtZXMpCiAgICBleHBlY3RhdGlvbnNbWyJBbHBoYVciXV0gPC0gaDVyZWFkKGZpbGUsICJleHBlY3RhdGlvbnMvQWxwaGFXIilbdmlld19uYW1lc10KICBpZiAoIkFscGhhWiIgJWluJSBub2RlX25hbWVzKQogICAgZXhwZWN0YXRpb25zW1siQWxwaGFaIl1dIDwtIGg1cmVhZChmaWxlLCAiZXhwZWN0YXRpb25zL0FscGhhWiIpW2dyb3VwX25hbWVzXQogIGlmICgiU2lnbWEiICVpbiUgbm9kZV9uYW1lcykKICAgIGV4cGVjdGF0aW9uc1tbIlNpZ21hIl1dIDwtIGg1cmVhZChmaWxlLCAiZXhwZWN0YXRpb25zL1NpZ21hIikKICBpZiAoIloiICVpbiUgbm9kZV9uYW1lcykKICAgIGV4cGVjdGF0aW9uc1tbIloiXV0gPC0gaDVyZWFkKGZpbGUsICJleHBlY3RhdGlvbnMvWiIpW2dyb3VwX25hbWVzXQogIGlmICgiVyIgJWluJSBub2RlX25hbWVzKQogICAgZXhwZWN0YXRpb25zW1siVyJdXSA8LSBoNXJlYWQoZmlsZSwgImV4cGVjdGF0aW9ucy9XIilbdmlld19uYW1lc10KICBpZiAoIlRoZXRhVyIgJWluJSBub2RlX25hbWVzKQogICAgZXhwZWN0YXRpb25zW1siVGhldGFXIl1dIDwtIGg1cmVhZChmaWxlLCAiZXhwZWN0YXRpb25zL1RoZXRhVyIpW3ZpZXdfbmFtZXNdCiAgaWYgKCJUaGV0YVoiICVpbiUgbm9kZV9uYW1lcykKICAgIGV4cGVjdGF0aW9uc1tbIlRoZXRhWiJdXSA8LSBoNXJlYWQoZmlsZSwgImV4cGVjdGF0aW9ucy9UaGV0YVoiKVtncm91cF9uYW1lc10KICAjIGlmICgiVGF1IiAlaW4lIG5vZGVfbmFtZXMpCiAgIyAgIGV4cGVjdGF0aW9uc1tbIlRhdSJdXSA8LSBoNXJlYWQoZmlsZSwgImV4cGVjdGF0aW9ucy9UYXUiKQogIAogIG9iamVjdEBleHBlY3RhdGlvbnMgPC0gZXhwZWN0YXRpb25zCgogIAogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIExvYWQgbW9kZWwgb3B0aW9ucyAjIwogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKICBpZiAodmVyYm9zZSkgbWVzc2FnZSgiTG9hZGluZyBtb2RlbCBvcHRpb25zLi4uIikKCiAgdHJ5Q2F0Y2goIHsKICAgIG9iamVjdEBtb2RlbF9vcHRpb25zIDwtIGFzLmxpc3QoaDVyZWFkKGZpbGUsICdtb2RlbF9vcHRpb25zJywgcmVhZC5hdHRyaWJ1dGVzID0gVFJVRSkpCiAgfSwgZXJyb3IgPSBmdW5jdGlvbih4KSB7IHByaW50KCJNb2RlbCBvcHRpb25zIG5vdCBmb3VuZCwgbm90IGxvYWRpbmcgaXQuLi4iKSB9KQoKICAjIENvbnZlcnQgVHJ1ZS9GYWxzZSBzdHJpbmdzIHRvIGxvZ2ljYWwgdmFsdWVzCiAgZm9yIChpIGluIG5hbWVzKG9iamVjdEBtb2RlbF9vcHRpb25zKSkgewogICAgaWYgKG9iamVjdEBtb2RlbF9vcHRpb25zW2ldID09ICJGYWxzZSIgfHwgb2JqZWN0QG1vZGVsX29wdGlvbnNbaV0gPT0gIlRydWUiKSB7CiAgICAgIG9iamVjdEBtb2RlbF9vcHRpb25zW2ldIDwtIGFzLmxvZ2ljYWwob2JqZWN0QG1vZGVsX29wdGlvbnNbaV0pCiAgICB9IGVsc2UgewogICAgICBvYmplY3RAbW9kZWxfb3B0aW9uc1tpXSA8LSBvYmplY3RAbW9kZWxfb3B0aW9uc1tpXQogICAgfQogIH0KCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyMgTG9hZCB0cmFpbmluZyBvcHRpb25zIGFuZCBzdGF0aXN0aWNzICMjCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgogIGlmICh2ZXJib3NlKSBtZXNzYWdlKCJMb2FkaW5nIHRyYWluaW5nIG9wdGlvbnMgYW5kIHN0YXRpc3RpY3MuLi4iKQoKICAjIExvYWQgdHJhaW5pbmcgb3B0aW9ucwogIGlmIChsZW5ndGgob2JqZWN0QHRyYWluaW5nX29wdGlvbnMpID09IDApIHsKICAgIHRyeUNhdGNoKCB7CiAgICAgIG9iamVjdEB0cmFpbmluZ19vcHRpb25zIDwtIGFzLmxpc3QoaDVyZWFkKGZpbGUsICd0cmFpbmluZ19vcHRzJywgcmVhZC5hdHRyaWJ1dGVzID0gVFJVRSkpCiAgICB9LCBlcnJvciA9IGZ1bmN0aW9uKHgpIHsgcHJpbnQoIlRyYWluaW5nIG9wdHMgbm90IGZvdW5kLCBub3QgbG9hZGluZyBpdC4uLiIpIH0pCiAgfQoKICAjIExvYWQgdHJhaW5pbmcgc3RhdGlzdGljcwogIHRyeUNhdGNoKCB7CiAgICBvYmplY3RAdHJhaW5pbmdfc3RhdHMgPC0gaDVyZWFkKGZpbGUsICd0cmFpbmluZ19zdGF0cycsIHJlYWQuYXR0cmlidXRlcyA9IFRSVUUpCiAgICBvYmplY3RAdHJhaW5pbmdfc3RhdHMgPC0gaDVyZWFkKGZpbGUsICd0cmFpbmluZ19zdGF0cycsIHJlYWQuYXR0cmlidXRlcyA9IFRSVUUpCiAgfSwgZXJyb3IgPSBmdW5jdGlvbih4KSB7IHByaW50KCJUcmFpbmluZyBzdGF0cyBub3QgZm91bmQsIG5vdCBsb2FkaW5nIGl0Li4uIikgfSkKCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIyBMb2FkIGNvdmFyaWF0ZXMgb3B0aW9ucyAjIwogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyAKICAjIGlmIChhbnkoZ3JlcGwoImNvdl9zYW1wbGVzIiwgaDVscy5vdXQkZ3JvdXApKSkgeyAKICAjICAgaWYgKGlzVFJVRSh2ZXJib3NlKSkgbWVzc2FnZSgiTG9hZGluZyBjb3ZhcmlhdGVzIG9wdGlvbnMuLi4iKQogICMgICB0cnlDYXRjaCggewogICMgICAgIG9iamVjdEBtZWZpc3RvX29wdGlvbnMgPC0gYXMubGlzdChoNXJlYWQoZmlsZSwgJ3Ntb290aF9vcHRzJywgcmVhZC5hdHRyaWJ1dGVzID0gVFJVRSkpCiAgIyAgIH0sIGVycm9yID0gZnVuY3Rpb24oeCkgeyBwcmludCgiQ292YXJpYXRlcyBvcHRpb25zIG5vdCBmb3VuZCwgbm90IGxvYWRpbmcgaXQuLi4iKSB9KQogICMgICAKICAjICAgIyBDb252ZXJ0IFRydWUvRmFsc2Ugc3RyaW5ncyB0byBsb2dpY2FsIHZhbHVlcwogICMgICBmb3IgKGkgaW4gbmFtZXMob2JqZWN0QG1lZmlzdG9fb3B0aW9ucykpIHsKICAjICAgICBpZiAob2JqZWN0QG1lZmlzdG9fb3B0aW9uc1tpXSA9PSAiRmFsc2UiIHwgb2JqZWN0QG1lZmlzdG9fb3B0aW9uc1tpXSA9PSAiVHJ1ZSIpIHsKICAjICAgICAgIG9iamVjdEBtZWZpc3RvX29wdGlvbnNbaV0gPC0gYXMubG9naWNhbChvYmplY3RAbWVmaXN0b19vcHRpb25zW2ldKQogICMgICAgIH0gZWxzZSB7CiAgIyAgICAgICBvYmplY3RAbWVmaXN0b19vcHRpb25zW2ldIDwtIG9iamVjdEBtZWZpc3RvX29wdGlvbnNbaV0KICAjICAgICB9CiAgIyAgIH0KICAjICAgCiAgIyB9CiAgIyAKICAKICAgIAogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIExvYWQgdmFyaWFuY2UgZXhwbGFpbmVkIGVzdGltYXRlcyAjIwogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogIAogIGlmICgidmFyaWFuY2VfZXhwbGFpbmVkIiAlaW4lIGg1bHMub3V0JG5hbWUpIHsKICAgIHIyX2xpc3QgPC0gbGlzdCgKICAgICAgcjJfdG90YWwgPSBoNXJlYWQoZmlsZSwgInZhcmlhbmNlX2V4cGxhaW5lZC9yMl90b3RhbCIpW2dyb3VwX25hbWVzXSwKICAgICAgcjJfcGVyX2ZhY3RvciA9IGg1cmVhZChmaWxlLCAidmFyaWFuY2VfZXhwbGFpbmVkL3IyX3Blcl9mYWN0b3IiKVtncm91cF9uYW1lc10KICAgICkKICAgIG9iamVjdEBjYWNoZVtbInZhcmlhbmNlX2V4cGxhaW5lZCJdXSA8LSByMl9saXN0CiAgfQogIAogICMgSGFjayB0byBmaXggdGhlIHByb2JsZW1zIHdoZXJlIHZhcmlhbmNlIGV4cGxhaW5lZCB2YWx1ZXMgcmFuZ2UgZnJvbSAwIHRvIDEgKCUpCiAgaWYgKG1heChzYXBwbHkob2JqZWN0QGNhY2hlJHZhcmlhbmNlX2V4cGxhaW5lZCRyMl90b3RhbCxtYXgsbmEucm09VFJVRSksbmEucm09VFJVRSk8MSkgewogICAgZm9yIChtIGluIDE6bGVuZ3RoKHZpZXdfbmFtZXMpKSB7CiAgICAgIGZvciAoZyBpbiAxOmxlbmd0aChncm91cF9uYW1lcykpIHsKICAgICAgICBvYmplY3RAY2FjaGUkdmFyaWFuY2VfZXhwbGFpbmVkJHIyX3RvdGFsW1tnXV1bW21dXSA8LSAxMDAgKiBvYmplY3RAY2FjaGUkdmFyaWFuY2VfZXhwbGFpbmVkJHIyX3RvdGFsW1tnXV1bW21dXQogICAgICAgIG9iamVjdEBjYWNoZSR2YXJpYW5jZV9leHBsYWluZWQkcjJfcGVyX2ZhY3RvcltbZ11dWyxtXSA8LSAxMDAgKiBvYmplY3RAY2FjaGUkdmFyaWFuY2VfZXhwbGFpbmVkJHIyX3Blcl9mYWN0b3JbW2ddXVssbV0KICAgICAgfQogICAgfQogIH0KICAKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIyBTcGVjaWZ5IGRpbWVuc2lvbmFsaXRpZXMgIyMKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAKICAjIFNwZWNpZnkgZGltZW5zaW9uYWxpdHkgb2YgdGhlIGRhdGEKICBvYmplY3RAZGltZW5zaW9uc1tbIk0iXV0gPC0gbGVuZ3RoKGRhdGEpICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbnVtYmVyIG9mIHZpZXdzCiAgb2JqZWN0QGRpbWVuc2lvbnNbWyJHIl1dIDwtIGxlbmd0aChkYXRhW1sxXV0pICAgICAgICAgICAgICAgICAgICAgICAjIG51bWJlciBvZiBncm91cHMKICBvYmplY3RAZGltZW5zaW9uc1tbIk4iXV0gPC0gc2FwcGx5KGRhdGFbWzFdXSwgbmNvbCkgICAgICAgICAgICAgICAgICMgbnVtYmVyIG9mIHNhbXBsZXMgKHBlciBncm91cCkKICBvYmplY3RAZGltZW5zaW9uc1tbIkQiXV0gPC0gc2FwcGx5KGRhdGEsIGZ1bmN0aW9uKGUpIG5yb3coZVtbMV1dKSkgICMgbnVtYmVyIG9mIGZlYXR1cmVzIChwZXIgdmlldykKICAjIG9iamVjdEBkaW1lbnNpb25zW1siQyJdXSA8LSBucm93KGNvdmFyaWF0ZXNbWzFdXSkgICAgICAgICAgICAgICAgICAgICAgICAjIG51bWJlciBvZiBjb3ZhcmlhdGVzCiAgb2JqZWN0QGRpbWVuc2lvbnNbWyJLIl1dIDwtIG5jb2wob2JqZWN0QGV4cGVjdGF0aW9ucyRaW1sxXV0pICAgICAgICAjIG51bWJlciBvZiBmYWN0b3JzCiAgCiAgIyBBc3NpZ24gc2FtcGxlIGFuZCBmZWF0dXJlIG5hbWVzIChzbG93IGZvciBsYXJnZSBtYXRyaWNlcykKICBpZiAodmVyYm9zZSkgbWVzc2FnZSgiQXNzaWduaW5nIG5hbWVzIHRvIHRoZSBkaWZmZXJlbnQgZGltZW5zaW9ucy4uLiIpCgogICMgQ3JlYXRlIGRlZmF1bHQgZmVhdHVyZXMgbmFtZXMgaWYgdGhleSBhcmUgbnVsbAogIGlmIChpcy5udWxsKGZlYXR1cmVfbmFtZXMpKSB7CiAgICBwcmludCgiRmVhdHVyZXMgbmFtZXMgbm90IGZvdW5kLCBnZW5lcmF0aW5nIGRlZmF1bHQ6IGZlYXR1cmUxX3ZpZXcxLCAuLi4sIGZlYXR1cmVEX3ZpZXdNIikKICAgIGZlYXR1cmVfbmFtZXMgPC0gbGFwcGx5KHNlcV9sZW4ob2JqZWN0QGRpbWVuc2lvbnNbWyJNIl1dKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKG0pIHNwcmludGYoImZlYXR1cmUlZF92aWV3XyZkIiwgYXMuY2hhcmFjdGVyKHNlcV9sZW4ob2JqZWN0QGRpbWVuc2lvbnNbWyJEIl1dW21dKSksIG0pKQogIH0gZWxzZSB7CiAgICAjIENoZWNrIGR1cGxpY2F0ZWQgZmVhdHVyZXMgbmFtZXMKICAgIGFsbF9uYW1lcyA8LSB1bm5hbWUodW5saXN0KGZlYXR1cmVfbmFtZXMpKQogICAgZHVwbGljYXRlZF9uYW1lcyA8LSB1bmlxdWUoYWxsX25hbWVzW2R1cGxpY2F0ZWQoYWxsX25hbWVzKV0pCiAgICBpZiAobGVuZ3RoKGR1cGxpY2F0ZWRfbmFtZXMpPjApIAogICAgICB3YXJuaW5nKCJUaGVyZSBhcmUgZHVwbGljYXRlZCBmZWF0dXJlcyBuYW1lcyBhY3Jvc3MgZGlmZmVyZW50IHZpZXdzLiBXZSB3aWxsIGFkZCB0aGUgc3VmZml4ICpfdmlldyogb25seSBmb3IgdGhvc2UgZmVhdHVyZXMgCiAgICAgICAgICAgIEV4YW1wbGU6IGlmIHlvdSBoYXZlIGJvdGggVFA1MyBpbiBtUk5BIGFuZCBtdXRhdGlvbiBkYXRhIGl0IHdpbGwgYmUgcmVuYW1lZCB0byBUUDUzX21STkEsIFRQNTNfbXV0YXRpb24iKQogICAgZm9yIChtIGluIG5hbWVzKGZlYXR1cmVfbmFtZXMpKSB7CiAgICAgIHRtcCA8LSB3aGljaChmZWF0dXJlX25hbWVzW1ttXV0gJWluJSBkdXBsaWNhdGVkX25hbWVzKQogICAgICBpZiAobGVuZ3RoKHRtcCk+MCkgZmVhdHVyZV9uYW1lc1tbbV1dW3RtcF0gPC0gcGFzdGUoZmVhdHVyZV9uYW1lc1tbbV1dW3RtcF0sIG0sIHNlcD0iXyIpCiAgICB9CiAgfQogIGZlYXR1cmVzX25hbWVzKG9iamVjdCkgPC0gZmVhdHVyZV9uYW1lcwogIAogICMgQ3JlYXRlIGRlZmF1bHQgc2FtcGxlcyBuYW1lcyBpZiB0aGV5IGFyZSBudWxsCiAgaWYgKGlzLm51bGwoc2FtcGxlX25hbWVzKSkgewogICAgcHJpbnQoIlNhbXBsZXMgbmFtZXMgbm90IGZvdW5kLCBnZW5lcmF0aW5nIGRlZmF1bHQ6IHNhbXBsZTEsIC4uLiwgc2FtcGxlTiIpCiAgICBzYW1wbGVfbmFtZXMgPC0gbGFwcGx5KG9iamVjdEBkaW1lbnNpb25zW1siTiJdXSwgZnVuY3Rpb24obikgcGFzdGUwKCJzYW1wbGUiLCBhcy5jaGFyYWN0ZXIoc2VxX2xlbihuKSkpKQogIH0KICBzYW1wbGVzX25hbWVzKG9iamVjdCkgPC0gc2FtcGxlX25hbWVzCgogICMgQWRkIGNvdmFyaWF0ZXMgbmFtZXMKICAjIGlmKCFpcy5udWxsKG9iamVjdEBjb3ZhcmlhdGVzKSl7CiAgIyAgICMgQ3JlYXRlIGRlZmF1bHQgY292YXJpYXRlcyBuYW1lcyBpZiB0aGV5IGFyZSBudWxsCiAgIyAgIGlmIChpcy5udWxsKGNvdmFyaWF0ZV9uYW1lcykpIHsKICAjICAgICBwcmludCgiQ292YXJpYXRlIG5hbWVzIG5vdCBmb3VuZCwgZ2VuZXJhdGluZyBkZWZhdWx0OiBjb3ZhcmlhdGUxLCAuLi4sIGNvdmFyaWF0ZUMiKQogICMgICAgIGNvdmFyaWF0ZV9uYW1lcyA8LSBwYXN0ZTAoInNhbXBsZSIsIGFzLmNoYXJhY3RlcihzZXFfbGVuKG9iamVjdEBkaW1lbnNpb25zW1siQyJdXSkpKQogICMgICB9CiAgIyAgIGNvdmFyaWF0ZXNfbmFtZXMob2JqZWN0KSA8LSBjb3ZhcmlhdGVfbmFtZXMKICAjIH0KICAKICAjIFNldCB2aWV3cyBuYW1lcwogIGlmIChpcy5udWxsKG5hbWVzKG9iamVjdEBkYXRhKSkpIHsKICAgIHByaW50KCJWaWV3cyBuYW1lcyBub3QgZm91bmQsIGdlbmVyYXRpbmcgZGVmYXVsdDogdmlldzEsIC4uLiwgdmlld00iKQogICAgdmlld19uYW1lcyA8LSBwYXN0ZTAoInZpZXciLCBhcy5jaGFyYWN0ZXIoc2VxX2xlbihvYmplY3RAZGltZW5zaW9uc1tbIk0iXV0pKSkKICB9CiAgdmlld3NfbmFtZXMob2JqZWN0KSA8LSB2aWV3X25hbWVzCiAgCiAgIyBTZXQgZ3JvdXBzIG5hbWVzCiAgaWYgKGlzLm51bGwobmFtZXMob2JqZWN0QGRhdGFbWzFdXSkpKSB7CiAgICBwcmludCgiR3JvdXBzIG5hbWVzIG5vdCBmb3VuZCwgZ2VuZXJhdGluZyBkZWZhdWx0OiBncm91cDEsIC4uLiwgZ3JvdXBHIikKICAgIGdyb3VwX25hbWVzIDwtIHBhc3RlMCgiZ3JvdXAiLCBhcy5jaGFyYWN0ZXIoc2VxX2xlbihvYmplY3RAZGltZW5zaW9uc1tbIkciXV0pKSkKICB9CiAgZ3JvdXBzX25hbWVzKG9iamVjdCkgPC0gZ3JvdXBfbmFtZXMKICAKICAjIFNldCBmYWN0b3JzIG5hbWVzCiAgZmFjdG9yc19uYW1lcyhvYmplY3QpICA8LSBwYXN0ZTAoIkZhY3RvciIsIGFzLmNoYXJhY3RlcihzZXFfbGVuKG9iamVjdEBkaW1lbnNpb25zW1siSyJdXSkpKQogIAogICMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIyBQYXJzZSBmYWN0b3JzICMjCiAgIyMjIyMjIyMjIyMjIyMjIyMjIwogIAogICMgQ2FsY3VsYXRlIHZhcmlhbmNlIGV4cGxhaW5lZCBlc3RpbWF0ZXMgcGVyIGZhY3RvcgogIGlmIChpcy5udWxsKG9iamVjdEBjYWNoZVtbInZhcmlhbmNlX2V4cGxhaW5lZCJdXSkpIHsKICAgIG9iamVjdEBjYWNoZVtbInZhcmlhbmNlX2V4cGxhaW5lZCJdXSA8LSBjYWxjdWxhdGVfdmFyaWFuY2VfZXhwbGFpbmVkKG9iamVjdCkKICB9IAogIAogICMgUmVtb3ZlIGluYWN0aXZlIGZhY3RvcnMKICBpZiAocmVtb3ZlX2luYWN0aXZlX2ZhY3RvcnMpIHsKICAgIHIyIDwtIHJvd1N1bXMoZG8uY2FsbCgnY2JpbmQnLCBsYXBwbHkob2JqZWN0QGNhY2hlW1sidmFyaWFuY2VfZXhwbGFpbmVkIl1dJHIyX3Blcl9mYWN0b3IsIHJvd1N1bXMsIG5hLnJtPVRSVUUpKSkKICAgIHZhci50aHJlc2hvbGQgPC0gMC4wMDAxCiAgICBpZiAoYWxsKHIyIDwgdmFyLnRocmVzaG9sZCkpIHsKICAgICAgd2FybmluZyhzcHJpbnRmKCJBbGwgJXMgZmFjdG9ycyB3ZXJlIGZvdW5kIHRvIGV4cGxhaW4gbGl0dGxlIG9yIG5vIHZhcmlhbmNlIHNvIHJlbW92ZV9pbmFjdGl2ZV9mYWN0b3JzIG9wdGlvbiBoYXMgYmVlbiBkaXNhYmxlZC4iLCBsZW5ndGgocjIpKSkKICAgIH0gZWxzZSBpZiAoYW55KHIyIDwgdmFyLnRocmVzaG9sZCkpIHsKICAgICAgb2JqZWN0IDwtIHN1YnNldF9mYWN0b3JzKG9iamVjdCwgd2hpY2gocjI+PXZhci50aHJlc2hvbGQpKQogICAgICBtZXNzYWdlKHNwcmludGYoIiVzIGZhY3RvcnMgd2VyZSBmb3VuZCB0byBleHBsYWluIG5vIHZhcmlhbmNlIGFuZCB0aGV5IHdlcmUgcmVtb3ZlZCBmb3IgZG93bnN0cmVhbSBhbmFseXNpcy4gWW91IGNhbiBkaXNhYmxlIHRoaXMgb3B0aW9uIGJ5IHNldHRpbmcgbG9hZF9tb2RlbCguLi4sIHJlbW92ZV9pbmFjdGl2ZV9mYWN0b3JzID0gRkFMU0UpIiwgc3VtKHIyIDwgdmFyLnRocmVzaG9sZCkpKQogICAgfQogIH0KICAKICAjIFtEb25lIGluIG1vZmFweTJdIFNvcnQgZmFjdG9ycyBieSB0b3RhbCB2YXJpYW5jZSBleHBsYWluZWQKICBpZiAoc29ydF9mYWN0b3JzICYmIG9iamVjdEBkaW1lbnNpb25zJEs+MSkgewoKICAgICMgU2FuaXR5IGNoZWNrcwogICAgaWYgKHZlcmJvc2UpIG1lc3NhZ2UoIlJlLW9yZGVyaW5nIGZhY3RvcnMgYnkgdGhlaXIgdmFyaWFuY2UgZXhwbGFpbmVkLi4uIikKCiAgICAjIENhbGN1bGF0ZSB2YXJpYW5jZSBleHBsYWluZWQgcGVyIGZhY3RvciBhY3Jvc3MgYWxsIHZpZXdzCiAgICByMiA8LSByb3dTdW1zKHNhcHBseShvYmplY3RAY2FjaGVbWyJ2YXJpYW5jZV9leHBsYWluZWQiXV0kcjJfcGVyX2ZhY3RvciwgZnVuY3Rpb24oZSkgcm93U3VtcyhlLCBuYS5ybSA9IFRSVUUpKSkKICAgIG9yZGVyX2ZhY3RvcnMgPC0gYyhuYW1lcyhyMilbb3JkZXIocjIsIGRlY3JlYXNpbmcgPSBUUlVFKV0pCgogICAgIyByZS1vcmRlciBmYWN0b3JzCiAgICBvYmplY3QgPC0gc3Vic2V0X2ZhY3RvcnMob2JqZWN0LCBvcmRlcl9mYWN0b3JzKQogIH0KCiAgIyBNYXNrIG91dGxpZXJzCiAgaWYgKHJlbW92ZV9vdXRsaWVycykgewogICAgaWYgKHZlcmJvc2UpIG1lc3NhZ2UoIlJlbW92aW5nIG91dGxpZXJzLi4uIikKICAgIG9iamVjdCA8LSAuZGV0ZWN0X291dGxpZXJzKG9iamVjdCkKICB9CiAgCiAgIyBNYXNrIGludGVyY2VwdHMgZm9yIG5vbi1HYXVzc2lhbiBkYXRhCiAgaWYgKGFueShvYmplY3RAbW9kZWxfb3B0aW9ucyRsaWtlbGlob29kcyE9ImdhdXNzaWFuIikpIHsKICAgIGZvciAobSBpbiBuYW1lcyh3aGljaChvYmplY3RAbW9kZWxfb3B0aW9ucyRsaWtlbGlob29kcyE9ImdhdXNzaWFuIikpKSB7CiAgICAgIGZvciAoZyBpbiBuYW1lcyhvYmplY3RAaW50ZXJjZXB0c1tbbV1dKSkgewogICAgICAgIG9iamVjdEBpbnRlcmNlcHRzW1ttXV1bW2ddXSA8LSBOQQogICAgICB9CiAgICB9CiAgfQoKICAjICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjICMjIFF1YWxpdHkgY29udHJvbHMgIyMKICAjICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIAogICMgaWYgKHZlcmJvc2UpIG1lc3NhZ2UoIkRvaW5nIHF1YWxpdHkgY29udHJvbC4uLiIpCiAgIyBvYmplY3QgPC0gLnF1YWxpdHlfY29udHJvbChvYmplY3QsIHZlcmJvc2UgPSB2ZXJib3NlKQogICMgCiAgcmV0dXJuKG9iamVjdCkKfQoKbW9mYV90cmFpbmVkIDwtIGxvYWRfbW9kZWwob3V0ZmlsZSkKCnNhbXBsZXNfbmFtZXMobW9mYV90cmFpbmVkKSA8LSBzYW1wbGVzX25hbWVzKG9iamVjdCkKc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpCnJvd25hbWVzKHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSkgPC0gc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpW1sic2FtcGxlIl1dCgoKYGBgCgoKIyMjIFBydW5lIGZhY3RvcnMKCiMjIyMgVmlzdWFsaXplIHZhcmlhbmNlIGV4cGxhaW5lZCBieSBmYWN0b3JzCgpgYGB7cn0KZ2V0X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIGFzLmRhdGEuZnJhbWUgPSBUUlVFLCApW1sxXV0gJT4lCiAgZHBseXI6Om11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscz1yZXYoYW5ub19vcmRlcikpKSAlPiUgCiAgZ2dwbG90KGFlcyhmYWN0b3IsZ3JvdXAsIGZpbGw9dmFsdWUpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCBoanVzdD0xKSkKCmdldF92YXJpYW5jZV9leHBsYWluZWQobW9mYV90cmFpbmVkLCBhcy5kYXRhLmZyYW1lID0gVFJVRSwgKVtbMl1dICU+JQogIGRwbHlyOjptdXRhdGUoZ3JvdXA9ZmFjdG9yKGdyb3VwLCBsZXZlbHM9YW5ub19vcmRlcikpICU+JQogIGdncGxvdChhZXMoZ3JvdXAsIHZhbHVlKSkgKwogIGdlb21fY29sKCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgeWxhYigiVmFyLiAoJSkiKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemU9MTQpCmBgYAoKCiMjIyMgSWRlbnRpZnkgdGVjaG5pY2FsIGZhY3RvcnMKCkRvIGZhY3RvcnMgcmVsYXRlIHRvIHRoZSBudW1iZXIgb2YgY2VsbHMgaW4gcHNldWRvYnVsaz8KYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD00fQpuX2NlbGxzIDwtIG1vZmFfdHJhaW5lZEBzYW1wbGVzX21ldGFkYXRhWywnbl9jZWxscycsIGRyb3A9RkFMU0VdClogPC0gZ2V0X2ZhY3RvcnMobW9mYV90cmFpbmVkKQpaIDwtIHB1cnJyOjpyZWR1Y2UoWiwgcmJpbmQpCmJhcnBsb3QoY29yKG5fY2VsbHMsIFopKQpgYGAKCkRpc3Rpbmd1aXNoIHRlY2huaWNhbCBmYWN0b3JzIGJ5IHdlaWdodCBzcGFyc2l0eQoKYGBge3J9CmdldF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgYWJzPVRSVUUsIHNjYWxlID0gRkFMU0UsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoZmFjdG9yKSAlPiUKICBkcGx5cjo6bXV0YXRlKHZhbHVlPSh2YWx1ZSAtIG1pbih2YWx1ZSkpLyhtYXgodmFsdWUpLSBtaW4odmFsdWUpKSwgcmFuaz1yYW5rKHZhbHVlKSkgJT4lCiAgZHBseXI6OnN1bW1hcmlzZShmcmFjX3plcm9zPXN1bSh2YWx1ZSA8IDAuMDUpL2RwbHlyOjpuKCkpICU+JQogIGdncGxvdChhZXMoZmFjdG9yLCBmcmFjX3plcm9zKSkgKwogIGdlb21fY29sKCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBjb2xvcj0icmVkIikKYGBgCgpgYGB7cn0KZXhjbHVkZV9mYWN0b3JzIDwtIGdldF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgYWJzPVRSVUUsIHNjYWxlID0gRkFMU0UsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoZmFjdG9yKSAlPiUKICBkcGx5cjo6bXV0YXRlKHZhbHVlPSh2YWx1ZSAtIG1pbih2YWx1ZSkpLyhtYXgodmFsdWUpLSBtaW4odmFsdWUpKSwgcmFuaz1yYW5rKHZhbHVlKSkgJT4lCiAgZHBseXI6OnN1bW1hcmlzZShmcmFjX3plcm9zPXN1bSh2YWx1ZSA8IDAuMDUpL2RwbHlyOjpuKCkpICU+JQogIGRwbHlyOjpmaWx0ZXIoZnJhY196ZXJvcyA8IDAuNTUpICU+JQogIGRwbHlyOjpwdWxsKGZhY3RvcikKYGBgCgoKYGBge3J9CmhpZ2hfcjJfZ3JvdXBzX2RmIDwtIGdldF92YXJpYW5jZV9leHBsYWluZWQobW9mYV90cmFpbmVkLCBhcy5kYXRhLmZyYW1lID0gVFJVRSlbWzFdXSAlPiUKICBkcGx5cjo6ZmlsdGVyKCFmYWN0b3IgJWluJSBleGNsdWRlX2ZhY3RvcnMpICU+JQogIGRwbHlyOjpncm91cF9ieShncm91cCkgJT4lCiAgZHBseXI6Om11dGF0ZSh0b3RfdmFyPXN1bSh2YWx1ZSkpICU+JQogIGRwbHlyOjp1bmdyb3VwKCkgJT4lCiAgZHBseXI6OmFycmFuZ2UodG90X3ZhcikgJT4lCiAgZHBseXI6Om11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscyA9IHVuaXF1ZShncm91cCkpKSAlPiUKICBkcGx5cjo6bXV0YXRlKHZhbHVlPWlmZWxzZSh2YWx1ZSA8IDUsIE5BLCB2YWx1ZSkpICU+JQogIGRwbHlyOjpmaWx0ZXIoIWlzLm5hKHZhbHVlKSkgCgpoaWdoX3IyX2dyb3Vwc19kZiAlPiUKICBnZ3Bsb3QoYWVzKGZhY3Rvcixncm91cCwgZmlsbD12YWx1ZSkpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3JzPWMoImdyYXk5NyIsImRhcmtibHVlIiksIGd1aWRlPSJjb2xvcmJhciIpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCBoanVzdD0xKSkgCiAgCmBgYAoKIyMjIyBGaW5kIGZhY3RvcnMgdGhhdCBzZXBhcmF0ZSBwc2V1ZG9idWxrcyBieSB0aXNzdWUKCkNhbGN1bGF0aW5nIEFkanVzdGVkIE11dHVhbCBJbmZvcm1hdGlvbiBiZXR3ZWVuIG9yZ2FuIGlkZW50aXR5IGFuZCBjbHVzdGVyaW5nIG9mIHBzZXVkb2J1bGtzIGJhc2VkIG9uIGZhY3RvciB2YWx1ZXMKCmBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9Nn0KY2FsY19vcmdhbl9BTUkgPC0gZnVuY3Rpb24oZiwgZyl7CiAgZG1hdCA8LSBkaXN0KGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGdyb3VwcyA9IGcpW1sxXV0pIAogIGhjbCA8LSBoY2x1c3QoZG1hdCkKICBuX29yZ2FucyA8LSBsZW5ndGgodW5pcXVlKHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKVtyb3duYW1lcyhhcy5tYXRyaXgoZG1hdCkpLCdvcmdhbiddKSkKICBoY2xfZGYgPC0gZGF0YS5mcmFtZShjbHVzdD1jdXRyZWUoaGNsLCBrPW5fb3JnYW5zKSkgJT4lCiAgICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlIikgJT4lCiAgICBkcGx5cjo6bGVmdF9qb2luKHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSkgCiAgb3JnYW5fQU1JIDwtIGFyaWNvZGU6OkFNSShoY2xfZGYkY2x1c3QsIGFzLm51bWVyaWMoaGNsX2RmJG9yZ2FuKSkKICByZXR1cm4ob3JnYW5fQU1JKQp9CgpzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkkb3JnYW4gPC0gYXMuZmFjdG9yKHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSRvcmdhbikKIyMgQ2FsYyBhZGp1c3RlZCBtdXR1YWwgaW5mbyBmb3IgZWFjaCBmYWN0b3IKQU1JcyA8LSBzYXBwbHkoMTpucm93KGhpZ2hfcjJfZ3JvdXBzX2RmKSwgZnVuY3Rpb24oaSkgY2FsY19vcmdhbl9BTUkoaGlnaF9yMl9ncm91cHNfZGYkZmFjdG9yW2ldLCBoaWdoX3IyX2dyb3Vwc19kZiRncm91cFtpXSkpCgojIyBDYWxjIGFkanVzdGVkIG11dHVhbCBpbmZvIGZvciBhbGwgZmFjdG9ycyB0aGF0IGV4cGxhbiA+IDIlIHZhcmlhbmNlIApncm91cHMgPC0gYXMuY2hhcmFjdGVyKHVuaXF1ZShoaWdoX3IyX2dyb3Vwc19kZiRncm91cCkpCmN0X0FNSXMgPC0gc2FwcGx5KGdyb3VwcywgZnVuY3Rpb24oZykgY2FsY19vcmdhbl9BTUkoaGlnaF9yMl9ncm91cHNfZGYkZmFjdG9yW2hpZ2hfcjJfZ3JvdXBzX2RmJGdyb3VwID09IGddLCBnKSkKCkFNSV9wbCA8LSBkYXRhLmZyYW1lKGN0X0FNSXMpICU+JQogIGRwbHlyOjphcnJhbmdlKGN0X0FNSXMpICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJjZWxsdHlwZSIpICU+JQogICMgZHBseXI6Om11dGF0ZShjZWxsdHlwZT1mYWN0b3Iocm93bmFtZSwgbGV2ZWxzPXVuaXF1ZShyb3duYW1lKSkpICU+JQpkcGx5cjo6bXV0YXRlKGNlbGx0eXBlPWZhY3RvcihjZWxsdHlwZSwgbGV2ZWxzPXJldihhbm5vX29yZGVyKSkpICU+JQogIGdncGxvdChhZXMoY3RfQU1JcywgY2VsbHR5cGUpKSArCiAgZ2VvbV9jb2woKSArCiAgeGxhYigiVG90YWwgT3JnYW4gQU1JIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpCiAgCkFNSV9mX3BsIDwtIGhpZ2hfcjJfZ3JvdXBzX2RmICU+JQogIGRwbHlyOjptdXRhdGUob3JnX0FNST1BTUlzKSAlPiUKICBkcGx5cjo6bXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzPWxldmVscyhBTUlfcGwkZGF0YSRjZWxsdHlwZSkpKSAlPiUKICBnZ3Bsb3QoYWVzKGZhY3RvciwgZ3JvdXAsIGZpbGw9b3JnX0FNSSkpICsKICBnZW9tX3RpbGUoY29sb3I9J2JsYWNrJykgKwogICMgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3JzPWMoImdyYXk5NyIsInJlZCIpLCBuYW1lPSJPcmdhbiBBZGouIE11dHVhbCBJbmZvIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbj0nbWFnbWEnKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCBoanVzdD0xKSkgICAKCkFNSV9mX3BsICsgKEFNSV9wbCArIHJlbW92ZV95X2F4aXMoKSkgKwogIHBsb3RfbGF5b3V0KGd1aWRlcz0iY29sbGVjdCIsIHdpZHRocyA9IGMoOCwzKSkKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD05LCBmaWcud2lkdGg9N30KIyMgU2F2ZSBpbmZvIG9uIE1JCmhpZ2hfcjJfZ3JvdXBzX2RmIDwtIGhpZ2hfcjJfZ3JvdXBzX2RmICU+JQogIGRwbHlyOjptdXRhdGUob3JnX0FNST1BTUlzKSAKCiMjIEZpeCBsb25nIG5hbWVzIGZvciBwbG90dGluZwphbGxfZ3JvdXBzIDwtIG5hbWVzKGdldF9kYXRhKG1vZmFfdHJhaW5lZClbWzFdXSkKZ3JvdXBfbGFiZWxsZXIgPC0gYWxsX2dyb3VwcyAlPiUKICBzdHJfcmVwbGFjZV9hbGwoIl8iLCAiICIpICU+JQogIHtpZmVsc2UobmNoYXIoLikgPiAyMCwgc3RyX3JlcGxhY2UoLiwgIiAiLCAiXG4iKSwgLil9ICU+JQogIHNldE5hbWVzKGFsbF9ncm91cHMpCgpBTUlfcGxfZGYgPC0gaGlnaF9yMl9ncm91cHNfZGYgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGdyb3VwKSAlPiUKICBkcGx5cjo6bXV0YXRlKG1lYW5fQU1JPW1heChvcmdfQU1JKSkgJT4lCiAgZHBseXI6OnVuZ3JvdXAoKSAlPiUKICBkcGx5cjo6YXJyYW5nZShtZWFuX0FNSSkgJT4lCiAgZHBseXI6Om11dGF0ZShncm91cD1ncm91cF9sYWJlbGxlclthcy5jaGFyYWN0ZXIoZ3JvdXApXSkgJT4lCiAgZHBseXI6Om11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscz11bmlxdWUoZ3JvdXApKSkgCgpBTUlfcGxfZGYgJT4lCiAgZ2dwbG90KGFlcyhvcmdfQU1JLCBncm91cCkpICsKICBnZW9tX3BvaW50KGFlcyhmaWxsPXZhbHVlKSwgc2l6ZT0zLCBzaGFwZT0yMSkgKwogIGdncmVwZWw6Omdlb21fdGV4dF9yZXBlbChhZXMobGFiZWw9c3RyX3JlbW92ZShmYWN0b3IsICJGYWN0b3IiKSksIGNvbG9yPSJibGFjayIsIGZvcmNlID0gMC4xLCBkaXJlY3Rpb24gPSAneCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG51ZGdlX3kgICAgICAgICAgID0gMC40LAogICAgaGp1c3QgICAgICAgICAgICAgPSAwKSArCiAgeGxhYigiQWRqLiBNdXR1YWwgSW5mb3JtYXRpb24gLSBPcmdhbiAiKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3VycyA9IGMoIndoaXRlIiwgInJlZCIpLCBuYW1lPSIlIHZhci4gZXhwbGFpbmVkIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE1KQoKCmBgYApgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OH0KZm9yIChmYWN0IGluIGFzLmNoYXJhY3Rlcih1bmlxdWUoQU1JX3BsX2RmJGZhY3RvcikpKXsKICBwIDwtIEFNSV9wbF9kZiAlPiUKICAgIGdncGxvdChhZXMob3JnX0FNSSwgZ3JvdXApKSArCiAgICBnZW9tX3BvaW50KGZpbGw9ImdyZXkiLCBzaXplPTIsIHNoYXBlPTIxLCBjb2xvcj0iZ3JleSIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IC4gJT4lIGRwbHlyOjpmaWx0ZXIoZmFjdG9yPT1mYWN0KSwKICAgICAgICAgICAgICAgICBhZXMoZmlsbD12YWx1ZSksIHNpemU9Mywgc2hhcGU9MjEpICsKICAgIHhsYWIoIkFkai4gTXV0dWFsIEluZm9ybWF0aW9uIC0gT3JnYW4gIikgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3VycyA9IGMoIndoaXRlIiwgInJlZCIpLCBuYW1lPSIlIHZhci4gZXhwbGFpbmVkIikgKwogICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTUpICsKICAgIGdndGl0bGUoZmFjdCkKICBwcmludChwKQogIH0KYGBgCiMjIyBFeHByZXNzaW9uIG9mIHRvcCBSMiBmYWN0b3JzCgpgYGB7cn0KZ2V0X3RvcF93ZWlnaHRfZ2VuZXMgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmLCBuX3RvcD0yMCwgd2hpY2g9InRvcCIpewogIHdfZGYgPC0gZ2V0X3dlaWdodHMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gZiwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpICU+JQogICAgZHBseXI6OmFycmFuZ2UodmFsdWUpIAogIHRvcF9nZW5lcyA8LSB3X2RmICU+JQogICAgICBkcGx5cjo6dG9wX24obl90b3AsIHZhbHVlKSAlPiUKICAgICAgZHBseXI6OnB1bGwoZmVhdHVyZSkgJT4lCiAgICAgIGFzLmNoYXJhY3RlcigpCiAgYm90X2dlbmVzIDwtICB3X2RmICU+JQogICAgICBkcGx5cjo6dG9wX24obl90b3AsIC12YWx1ZSkgJT4lCiAgICAgIGRwbHlyOjpwdWxsKGZlYXR1cmUpICU+JQogICAgICBhcy5jaGFyYWN0ZXIoKQogIGlmICh3aGljaD09InRvcCIpIHsKICAgIGdlbmVzIDwtIHRvcF9nZW5lcwogIH0gZWxzZSBpZiAod2hpY2g9PSJib3R0b20iKXsKICAgIGdlbmVzIDwtIGJvdF9nZW5lcwogIH0gZWxzZSBpZiAod2hpY2g9PSJib3RoIil7CiAgICBnZW5lcyA8LSBjKHRvcF9nZW5lcywgYm90X2dlbmVzKQogIH0KICByZXR1cm4oZ2VuZXMpCn0KCnBsb3RfZGF0YV90b3Bfd2VpZ2h0cyA8LSBmdW5jdGlvbihtb2ZhX3RyYWluZWQsIGN0LCBmLCBuX3RvcD0yMCwgd2hpY2g9InRvcCIpewogIGdlbmVzIDwtIGdldF90b3Bfd2VpZ2h0X2dlbmVzKG1vZmFfdHJhaW5lZCwgZiwgd2hpY2g9d2hpY2gsIG5fdG9wPW5fdG9wKQogIGRhdGEgPC0gZ2V0X2RhdGEobW9mYV90cmFpbmVkLCBncm91cHM9Y3QpW1sxXV1bWzFdXVtnZW5lcyxdCiAgCiAgcGxfZGYgPC0gcmVzaGFwZTI6Om1lbHQoZGF0YSwgdmFybmFtZXM9YygiZ2VuZSIsICJzYW1wbGUiKSkgJT4lCiAgICBkcGx5cjo6bGVmdF9qb2luKHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSkgJT4lCiAgICBkcGx5cjo6YXJyYW5nZShhZ2UpICU+JQogICAgZHBseXI6Om11dGF0ZShzYW1wbGU9ZmFjdG9yKHNhbXBsZSwgbGV2ZWxzPXVuaXF1ZShzYW1wbGUpKSkgJT4lCiAgICBkcGx5cjo6Z3JvdXBfYnkoZ2VuZSkgJT4lCiAgICBkcGx5cjo6bXV0YXRlKHZhbHVlPXNjYWxlKHZhbHVlKSkKICBwbF9kZiAlPiUKICAgIGdncGxvdChhZXMoc2FtcGxlLCBnZW5lLCBmaWxsPXZhbHVlKSkgKwogICAgZ2VvbV90aWxlKCkgKwogICAgZmFjZXRfZ3JpZCgufm9yZ2FuLCBzcGFjZT0iZnJlZSIsIHNjYWxlcz0iZnJlZSIpICsKICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGhpZ2g9InJlZCIsIGxvdz0iYmx1ZSIsIG5hbWU9IlNjYWxlZFxuZXhwcmVzc2lvbiIpICsKICAgIHhsYWIoIi0tLS1hZ2UtLS0+IikgKyB5bGFiKGdsdWUoInt3aGljaH0gd2VpZ2h0IGdlbmVzIikpICsKICAgIHRoZW1lX2J3KGJhc2Vfc2l6ZT0xNikgKwogICAgdGhlbWUoYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgZ2d0aXRsZShnbHVlKCd7Y3R9IC0ge2Z9JykpCn0KCmZvciAoZyBpbiBhbGxfZ3JvdXBzKXsKICBmcyA8LSBnZXRfdG9wX2ZhY3Rvcl9wZXJfY2VsbHR5cGUobW9mYV90cmFpbmVkLCBnLCBtaW5fUjI9NSkKICBmcyA8LSBmc1shZnMgJWluJSBleGNsdWRlX2ZhY3RvcnNdCiAgaWYgKGxlbmd0aChmcykgPiAwKXsKICAgIHRvcF9wbG90cyA8LSBsYXBwbHkoZnMsIGZ1bmN0aW9uKHgpIChwbG90X2RhdGFfdG9wX3dlaWdodHMobW9mYV90cmFpbmVkLCBnLCB4LCB3aGljaD0idG9wIikgKyByZW1vdmVfeF9heGlzKCkpIC8gIAogICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3RfZGF0YV90b3Bfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGcsIHgsIHdoaWNoPSJib3R0b20iKSArIGdndGl0bGUoIiIpCiAgICApCiAgICBmdWxsX3BsIDwtd3JhcF9wbG90cyh0b3BfcGxvdHMsIG5jb2w9MSkgCiAgICBnZ3NhdmUoZ2x1ZSgie2ZpZ2Rpcn0vdG9wX2ZhY3RvcnNfZXhwcl97Z30ucGRmIikscGxvdD1mdWxsX3BsLCAgd2lkdGg9MTIsIGhlaWdodCA9IDEwKmxlbmd0aCh0b3BfcGxvdHMpKQogICAgfSAgCn0KCmBgYAoKCjwhLS0gYGBge3J9IC0tPgo8IS0tICMjIHJlb3JkZXIgb3JnYW5zIC0tPgo8IS0tIHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSRvcmdhbiA8LSBmYWN0b3Ioc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpJG9yZ2FuLCBsZXZlbHM9b3JnYW5fb3JkZXIpIC0tPgo8IS0tIGZvciAoZyBpbiBhbGxfZ3JvdXBzKXsgLS0+CjwhLS0gICBmcyA8LSBnZXRfdG9wX2ZhY3Rvcl9wZXJfY2VsbHR5cGUobW9mYV90cmFpbmVkLCBnLCBtaW5fUjI9MikgLS0+CjwhLS0gICBmcyA8LSBmc1shZnMgJWluJSBleGNsdWRlX2ZhY3RvcnNdIC0tPgo8IS0tICAgaWYgKGxlbmd0aChmcykgPiAwKXsgLS0+CjwhLS0gICAgICAgcGwgPC0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLGdyb3VwcyA9IGMoZyksIGNvbG9yX2J5PSJvcmdhbiIsIGRvdF9zaXplID0gMywgZmFjdG9ycyA9IGZzLCAtLT4KPCEtLSAgICAgICAgICAgICBhZGRfYm94cGxvdCA9IFRSVUUsIGJveHBsb3RfYWxwaGEgPSAwLjEsIC0tPgo8IS0tICAgICAgICAgICAgIGdyb3VwX2J5ID0gIm9yZ2FuIiwgc2NhbGUgPSApICsgLS0+CjwhLS0gICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1vcmdfY29sb3JzKSArIC0tPgo8IS0tICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPW9yZ19jb2xvcnMpICsgLS0+CjwhLS0gICAgIGdndGl0bGUoZykgLS0+CjwhLS0gICAgIHByaW50KHBsKSAtLT4KPCEtLSAgIH0gLS0+CjwhLS0gICB9IC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3IsIGZpZy5oZWlnaHQ9MTUsIGZpZy53aWR0aD0xNX0gLS0+CjwhLS0gZ3JfdG9wX2ZhY3RvcnMgPC0gbGFwcGx5KGFsbF9ncm91cHMsIGZ1bmN0aW9uKGcpIGRhdGEuZnJhbWUoZ3JvdXA9ZywgdG9wX2ZhY3RvcnM9Z2V0X3RvcF9mYWN0b3JfcGVyX2NlbGx0eXBlKG1vZmFfdHJhaW5lZCwgZywgbWluX1IyPTIpKSkgJT4lIC0tPgo8IS0tICAgcHVycnI6OnJlZHVjZShkcGx5cjo6YmluZF9yb3dzKSAlPiUgLS0+CjwhLS0gICBkcGx5cjo6ZmlsdGVyKCF0b3BfZmFjdG9ycyAlaW4lIGV4Y2x1ZGVfZmFjdG9ycykgLS0+Cgo8IS0tIGZvciAoZiBpbiB1bmlxdWUoZ3JfdG9wX2ZhY3RvcnMkdG9wX2ZhY3RvcnMpKXsgLS0+CjwhLS0gICBncyA8LSBkcGx5cjo6ZmlsdGVyKGdyX3RvcF9mYWN0b3JzLCB0b3BfZmFjdG9ycz09ZikgJT4lIGRwbHlyOjpwdWxsKGdyb3VwKSAtLT4KPCEtLSAgIHBsX2xzIDwtIGxhcHBseShncywgZnVuY3Rpb24oZykgcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLGdyb3VwcyA9IGMoZyksIGNvbG9yX2J5PSJvcmdhbiIsIGRvdF9zaXplID0gMywgZmFjdG9ycyA9IGYsIC0tPgo8IS0tICAgICAgICAgICAgIGFkZF9ib3hwbG90ID0gVFJVRSwgYm94cGxvdF9hbHBoYSA9IDAuMSwgIC0tPgo8IS0tICAgICAgICAgICAgIGdyb3VwX2J5ID0gJ2dyb3VwJywgZG9kZ2UgPSBUUlVFKSArIC0tPgo8IS0tICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9b3JnX2NvbG9ycykgKyAtLT4KPCEtLSAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1vcmdfY29sb3JzKSArIC0tPgo8IS0tICAgICBnZ3RpdGxlKGcpKSAtLT4KPCEtLSAgcHJpbnQod3JhcF9wbG90cyhwbF9scykgKyBwbG90X2xheW91dChndWlkZXM9ImNvbGxlY3QiKSArIHBsb3RfYW5ub3RhdGlvbih0aXRsZT1mKSkgIC0tPgo8IS0tICAgfSAtLT4KPCEtLSBgYGAgLS0+CgoKCmBgYHtyLCBmaWcud2lkdGg9MTgsIGZpZy5oZWlnaHQ9N30KbWlubWF4X25vcm1hbGl6ZSA8LSBmdW5jdGlvbih4LCBuYS5ybSA9IFRSVUUpIHsKICAgIHJldHVybigoeC0gbWluKHgpKSAvKG1heCh4KS1taW4oeCkpKQp9CgpwbG90X2RhdGFfdG9wX3dlaWdodHNfY2x1c3RlcmVkIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgY3RzLCBmLCBuX3RvcD0yMCwgd2hpY2g9InRvcCIsIHNjYWxlX2RhdGE9VFJVRSl7CiAgZ2VuZXMgPC0gZ2V0X3RvcF93ZWlnaHRfZ2VuZXMobW9mYV90cmFpbmVkLCBmLCB3aGljaD13aGljaCwgbl90b3A9bl90b3ApCiAgCiAgZ2VuZXNfYW5ubyA8LSBkYXRhLmZyYW1lKGdlbmU9Z2VuZXMpIAogIGlmICh3aGljaCE9ImJvdGgiKXsgZ2VuZXNfYW5ub1tbIndlaWdodCJdXSA8LSByZXAod2hpY2gsIG5fdG9wKSB9ICBlbHNlIHsgZ2VuZXNfYW5ub1tbIndlaWdodCJdXSA8LSBjKHJlcCgidG9wIiwgbl90b3ApLCByZXAoImJvdHRvbSIsIG5fdG9wKSkgfQogIAogIGRhdGFfbHMgPC0gZ2V0X2RhdGEobW9mYV90cmFpbmVkLCBncm91cHM9Y3RzKVtbMV1dCiAgZGF0YSA8LSBSZWR1Y2UoY2JpbmQsIGRhdGFfbHMpW2dlbmVzLF0KICAKICBjdF9wbF9scyA8LSBsYXBwbHkoY3RzLCBmdW5jdGlvbihjdCl7CiAgICBjdF9zYW1wbGVzIDwtIGNvbG5hbWVzKG1vZmFfdHJhaW5lZEBkYXRhW1sxXV1bW2N0XV0pCiAgICBjdF9kYXRhIDwtIGRhdGFbLGN0X3NhbXBsZXNdCiAgICBpZiAoc2NhbGVfZGF0YSl7CiAgICAgIGN0X2RhdGEgPC0gdChhcHBseShjdF9kYXRhLCAxLCBtaW5tYXhfbm9ybWFsaXplKSkKICAgIH0KICAgIGNsX2hlYXRtYXAgPC0gcGhlYXRtYXA6OnBoZWF0bWFwKGN0X2RhdGEsIHNob3dfY29sbmFtZXM9IEZBTFNFLCBjbHVzdGVyX3Jvd3MgPSBGQUxTRSwgKQogICAgY29sX29yZGVyIDwtIGNsX2hlYXRtYXAkdHJlZV9jb2wkbGFiZWxzW2NsX2hlYXRtYXAkdHJlZV9jb2wkb3JkZXJdCiAgICAKICAgIHBsX2RmIDwtIHJlc2hhcGUyOjptZWx0KGN0X2RhdGEsIHZhcm5hbWVzPWMoImdlbmUiLCAic2FtcGxlIikpICU+JQogICAgICAgIGRwbHlyOjpsZWZ0X2pvaW4oc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpKSAlPiUKICAgICAgICBkcGx5cjo6bGVmdF9qb2luKGdlbmVzX2Fubm8pICU+JQogICAgICAgIGRwbHlyOjptdXRhdGUoc2FtcGxlPWZhY3RvcihzYW1wbGUsIGxldmVscz1jb2xfb3JkZXIpLAogICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0PWZhY3Rvcih3ZWlnaHQsIGxldmVscz1jKCJ0b3AiLCAiYm90dG9tIikpKSAKICAgIAogICAgcGxfYmFyIDwtIHBsX2RmICU+JSAKICAgICAgZ2dwbG90KGFlcyhzYW1wbGUsICJvcmdhbiIsIGZpbGw9b3JnYW4pKSArCiAgICAgIGdlb21fdGlsZSgpICsKICAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPW9yZ19jb2xvcnMpICsKICAgICAgdGhlbWVfdm9pZCgpICsKICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQogICAgcGxfaG0gPC0gcGxfZGYgJT4lCiAgICAgIGdncGxvdChhZXMoc2FtcGxlLCBnZW5lLCBmaWxsPXZhbHVlKSkgKwogICAgICAgIGdlb21fdGlsZSgpICsKICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb249Im1hZ21hIiwgbmFtZT0iU2NhbGVkXG5leHByZXNzaW9uIikgKwogICAgICAgIHhsYWIoZ3JvdXBfbGFiZWxsZXJbY3RdKSArCiAgICAgICAgZmFjZXRfZ3JpZCh3ZWlnaHR+Liwgc2NhbGVzPSJmcmVlIiwgc3BhY2U9ImZyZWUiKSArCiAgICAgICAgdGhlbWVfYncoYmFzZV9zaXplPTEyKSArCiAgICAgICAgdGhlbWUoYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkKICAgIChwbF9iYXIgLyBwbF9obSkgKyBwbG90X2xheW91dChoZWlnaHRzID0gYygxLDEwKSkKICAgIH0pCiAgaWYgKGxlbmd0aChjdF9wbF9scykgPiAxKXsKICAgICMjIFJlbW92ZSBnZW5lIG5hbWVzIHRvIGFsbCBleGNlcHQgMXN0IHBsb3QKICAgIGN0X3BsX2xzWzI6bGVuZ3RoKGN0X3BsX2xzKV0gPC0gbGFwcGx5KGN0X3BsX2xzWzI6bGVuZ3RoKGN0X3BsX2xzKV0sIGZ1bmN0aW9uKHApIHAgKyByZW1vdmVfeV9heGlzKCkpCiAgICAKICAgICMjIFJlbW92ZSBzdHJpcCBuYW1lcyB0byBhbGwgZXhjZXB0IGxhc3QgcGxvdAogICAgY3RfcGxfbHNbMToobGVuZ3RoKGN0X3BsX2xzKS0xKV0gPC0gbGFwcGx5KGN0X3BsX2xzWzE6KGxlbmd0aChjdF9wbF9scyktMSldLCBmdW5jdGlvbihwKSBwICsgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSxzdHJpcC50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpKQogICAgd3JhcF9wbG90cyhjdF9wbF9scykgKyAKICAgIHBsb3RfbGF5b3V0KGd1aWRlcz0iY29sbGVjdCIsIG5yb3cgPSAxKQogIH0gZWxzZSB7CiAgICBjdF9wbF9sc1tbMV1dCiAgfQp9CgpwbG90X2ZhY3Rvcl9vcmdhbl9ib3hwbG90cyA8LSBmdW5jdGlvbihmLCBjdHMpewogIHBsX2xzIDwtIGxhcHBseShjdHMsIGZ1bmN0aW9uKGcpIHBsb3RfZmFjdG9yKG1vZmFfdHJhaW5lZCxncm91cHMgPSBjKGcpLCBjb2xvcl9ieT0ib3JnYW4iLCBkb3Rfc2l6ZSA9IDMsIGZhY3RvcnMgPSBmLAogICAgICAgICAgICBhZGRfYm94cGxvdCA9IFRSVUUsIGJveHBsb3RfYWxwaGEgPSAwLjEsIAogICAgICAgICAgICBncm91cF9ieSA9ICdncm91cCcsIGRvZGdlID0gVFJVRSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPW9yZ19jb2xvcnMpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9b3JnX2NvbG9ycykgKwogICAgZ2d0aXRsZShncm91cF9sYWJlbGxlcltnXSkgCiAgICApCiB3cmFwX3Bsb3RzKHBsX2xzKSArIAogICBwbG90X2xheW91dChndWlkZXM9ImNvbGxlY3QiLCBucm93PTEpICsgCiAgIHBsb3RfYW5ub3RhdGlvbih0aXRsZT1mKSAKfQoKaGlnaF9yMl9ncm91cHNfZGZfZmlsdCA8LSBoaWdoX3IyX2dyb3Vwc19kZiAlPiUKICBkcGx5cjo6ZmlsdGVyKG9yZ19BTUkgPiAwLjMpICU+JQogIGRwbHlyOjphcnJhbmdlKC0gb3JnX0FNSSkgCgpmb3IgKGZhY3QgaW4gYXMuY2hhcmFjdGVyKHVuaXF1ZShoaWdoX3IyX2dyb3Vwc19kZl9maWx0JGZhY3RvcikpKXsKICBmYWN0X2N0cyA9IGFzLmNoYXJhY3RlcihoaWdoX3IyX2dyb3Vwc19kZl9maWx0JGdyb3VwW2hpZ2hfcjJfZ3JvdXBzX2RmX2ZpbHQkZmFjdG9yPT1mYWN0XSkKICBwX3RvcCA8LSBwbG90X2ZhY3Rvcl9vcmdhbl9ib3hwbG90cyhjdHM9ZmFjdF9jdHMsIGY9ZmFjdCkKICBwX2JvdHRvbSA8LSBwbG90X2RhdGFfdG9wX3dlaWdodHNfY2x1c3RlcmVkKG1vZmFfdHJhaW5lZCwgY3RzPWZhY3RfY3RzLCBmPWZhY3QsIHdoaWNoID0gImJvdGgiLCBzY2FsZV9kYXRhID0gVFJVRSkKICBmX3BsIDwtIChwX3RvcCAvIHBfYm90dG9tKSArCiAgICBwbG90X2xheW91dChoZWlnaHRzID0gYygxLDIuNSkpCiAgCiAgZ2dzYXZlKGdsdWUoIntmaWdkaXJ9L3tmYWN0fV90b3Bfb3JnYW5fQU1JX3Bsb3QucGRmIiksIHBsb3Q9Zl9wbCwgIHdpZHRoPTUgKyAoMypsZW5ndGgoZmFjdF9jdHMpKSwgaGVpZ2h0ID0gOSkKfQpgYGAKCgotLS0KLS0tCmBgYHtyfQpnZXRfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKSAlPiUKICBsZWZ0X2pvaW4oc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpKSAlPiUKICBtdXRhdGUoc29ydD1pZmVsc2Uoc3RyX2RldGVjdChTYW1wbGUsICJDRDQ1UCIpLCAiQ0Q0NSsiLCBpZmVsc2Uoc3RyX2RldGVjdChTYW1wbGUsIkNENDVOIiksICJDRDQ1LSIsIGlmZWxzZShzdHJfZGV0ZWN0KFNhbXBsZSwgIlRPVCIpLCAiVE9UIiwgIm90aGVyIikpKSkgJT4lCiAgZmlsdGVyKG9yZ2FuPT0iVEgiICYgZmFjdG9yPT0iRmFjdG9yMiIgJiBncm91cD09IkNEOEFBIikgJT4lCiAgZ2dwbG90KGFlcyh2YWx1ZSxTYW1wbGUsICBjb2xvcj1zb3J0KSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2d0aXRsZSgiVFJFRyIpICsKICB4bGFiKCJGYWN0b3IyIikgCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD03fQpwMSA8LSBnZXRfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKSAlPiUKICBsZWZ0X2pvaW4oc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpKSAlPiUKICBmaWx0ZXIob3JnYW49PSJUSCIgJiBmYWN0b3I9PSJGYWN0b3IyIiAmIGdyb3VwPT0iVFJFRyIpICU+JQogIGdncGxvdChhZXModmFsdWUsU2FtcGxlLCAgY29sb3I9YWdlKSkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwogIGdndGl0bGUoIlRSRUciKSArCiAgeGxhYigiRmFjdG9yMiIpIAoKcDIgPC0gZ2V0X2ZhY3RvcnMobW9mYV90cmFpbmVkLCBhcy5kYXRhLmZyYW1lID0gVFJVRSkgJT4lCiAgbGVmdF9qb2luKHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSkgJT4lCiAgZmlsdGVyKG9yZ2FuPT0iVEgiICYgZmFjdG9yPT0iRmFjdG9yMiIgJiBncm91cD09IlRIMTciKSAlPiUKICBnZ3Bsb3QoYWVzKHZhbHVlLFNhbXBsZSwgIGNvbG9yPWFnZSkpICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsKICBnZ3RpdGxlKCJUSDE3IikgKwogIHhsYWIoIkZhY3RvcjIiKSAKCnAzIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpICU+JQogIGxlZnRfam9pbihzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkpICU+JQogIGZpbHRlcihvcmdhbj09IlRIIiAmIGZhY3Rvcj09IkZhY3RvcjIiICYgZ3JvdXA9PSJDRDhBQSIpICU+JQogIGdncGxvdChhZXModmFsdWUsU2FtcGxlLCAgY29sb3I9YWdlKSkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwogIGdndGl0bGUoIkNEOEFBIikgKwogIHhsYWIoIkZhY3RvcjIiKQoKKHAxIC8gcDIgL3AzKSArIHBsb3RfbGF5b3V0KGd1aWRlcz0nY29sbGVjdCcpCmBgYAoKCiMjIyBWaXN1YWxpemUgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGZhY3RvcnMKCmBgYHtyfQpwbG90X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIHg9J2ZhY3RvcicsIHk9J2dyb3VwJywgc3BsaXRfYnkgPSAndmlldycsIHBsb3RfdG90YWwgPSBUUlVFLCBtYXhfcjIgPSA1MClbWzFdXSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpKQoKZ2V0X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMl1dICU+JQogIGdncGxvdChhZXMoZ3JvdXAsIHZhbHVlKSkgKwogIGdlb21fY29sKCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgeWxhYigiVmFyLiAoJSkiKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemU9MTQpCmBgYAoKUGxvdCBieSBjZWxsdHlwZQpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEwfQpnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpW1sxXV0gJT4lCiAgZ2dwbG90KGFlcyhmYWN0b3IsIHZhbHVlKSkgKyBnZW9tX2NvbCgpICsKICBjb29yZF9mbGlwKCkgKwogIGZhY2V0X3dyYXAoZ3JvdXB+LiwgbmNvbCA9IDYsIHNjYWxlcyA9ICJmcmVlX3giKQpgYGAKCmBgYHtyfQpwbG90X2ZhY3Rvcl9jb3IobW9mYV90cmFpbmVkLCBtZXRob2QgPSAic3BlYXJtYW4iKQpgYGAKCgpgYGB7cn0KIyMgQ29ycmVsYXRpb24gd2l0aCBwcmluY2lwYWwgY29tcG9uZW50cwpwY3MgPC0gcmVkdWNlZERpbShzY2UpCmZjdHJzIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCkgJT4lCiAgcHVycnI6OnJlZHVjZShyYmluZCkKCmNvcnJwbG90Ojpjb3JycGxvdChjb3IocGNzLCBmY3Ryc1tyb3duYW1lcyhwY3MpLF0pKQpgYGAKCiMjIyMgRmFjdG9yIElEIHBsb3RzCgpgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpwbG90X2ZhY3Rvcl9vcmRlcmVkIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZil7CiAgZmFjdG9yX2RmIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKSAlPiUKICAgICAgbXV0YXRlKG9yZ2FuID0gc2FwcGx5KHN0cl9zcGxpdChzYW1wbGUsICJfIiksIGZ1bmN0aW9uKHgpIHhbMl0pKSAlPiUKICAgICAgZ3JvdXBfYnkoZ3JvdXApICU+JQogICAgICBtdXRhdGUoZ3JfbWVhbiA9IG1lZGlhbih2YWx1ZSkpICU+JQogICAgICB1bmdyb3VwKCkgJT4lCiAgICAgIGFycmFuZ2UoZ3JfbWVhbikgJT4lCiAgICAgIG11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscz11bmlxdWUoZ3JvdXApKSkgCiAgCiAgcjJfZGYgPC0gZ2V0X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSBmLCBhcy5kYXRhLmZyYW1lID0gVFJVRSlbWzFdXSAlPiUKICAgIGZpbHRlcihmYWN0b3I9PXBhc3RlMCgnRmFjdG9yJyxmKSkgJT4lCiAgICBtdXRhdGUoZ3JvdXA9ZmFjdG9yKGdyb3VwLCBsZXZlbHMgPSBsZXZlbHMoZmFjdG9yX2RmJGdyb3VwKSkpCiAgCiAgcGwxIDwtIGZhY3Rvcl9kZiAlPiUKICAgICAgZ2dwbG90KGFlcyhncm91cCwgdmFsdWUpKSArCiAgICAgIGdlb21fYm94cGxvdCgpICsKICAgICAgZ2VvbV9qaXR0ZXIoYWVzKGNvbG9yPSBvcmdhbiksIHNpemU9MC43KSArCiAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlPTIpICsKICAgICAgY29vcmRfZmxpcCgpICsKICAgICAgeWxhYihwYXN0ZTAoIkZhY3RvciAiLCBmKSkgKwogICAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkKICAKICBwbDIgPC0gcjJfZGYgJT4lCiAgICBnZ3Bsb3QoYWVzKGdyb3VwLCB2YWx1ZSkpICsKICAgIGdlb21fY29sKCkgKwogICAgY29vcmRfZmxpcCgpICsKICAgIHlsYWIoIiUgdmFyaWFuY2UgZXhwbGFpbmVkIikgKwogICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpICsKICAgIHJlbW92ZV95X2F4aXMoKQogIAogIHBsMSArIHBsMiArIHBsb3RfbGF5b3V0KHdpZHRocz1jKDIsMSksIGd1aWRlcz0iY29sbGVjdCIpIAp9CgpnZXRfdG9wX2NlbGx0eXBlX3Blcl9mYWN0b3IgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmKXsKICByMl9kZiA8LSBnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMV1dICU+JQogICAgZmlsdGVyKGZhY3Rvcj09cGFzdGUwKCdGYWN0b3InLGYpKSAKICAgICMgbXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzID0gKSkKICB0b3BfcXVhbnRfcjIgPC0gcXVhbnRpbGUocjJfZGYkdmFsdWUsIHByb2JzID0gc2VxKDAsIDEsIGJ5ID0gMC4yKSlbIjgwJSJdCiAgdG9wX2dyb3VwcyA8LSByMl9kZiRncm91cFtyMl9kZiR2YWx1ZSA+PSB0b3BfcXVhbnRfcjJdCiAgcmV0dXJuKHRvcF9ncm91cHMpCn0KCnNhdmVfZmFjdG9yX2lkIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZiwgZmlnZGlyKXsKICAjIyBPcmRlciBjZWxsdHlwZXMgYnkgZmFjdG9yIHZhbHVlcwogIHAxIDwtIHBsb3RfZmFjdG9yX29yZGVyZWQobW9mYV90cmFpbmVkLCBmKQogIAogICMjIFBsb3QgZmFjdG9yIHZhbHVlcyBhY3Jvc3Mgb3JnYW5zIGZvciBjZWxsdHlwZXMgd2l0aCBoaWdoIHZhcmlhbmNlIGV4cGxhaW5lZAogIHAyIDwtIHBsb3RfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGdyb3VwcyA9IGdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIGYpLCBncm91cF9ieSA9ICJncm91cCIsIAogICAgICAgICAgICAgIGNvbG9yX2J5ID0gIm9yZ2FuIiwgCiAgICAgICAgICAgICAgZG90X3NpemUgPSAyLCBkb2RnZSA9IFRSVUUKICAgICAgICAgICAgICApCiAgCiAgIyMgUGxvdCBmYWN0b3Igd2VpZ2h0cyBvbiBnZW5lcwogICMgcGxvdF9kYXRhX2hlYXRtYXAobW9mYV90cmFpbmVkLCBmYWN0b3IgPSBmLCBuZmVhdHVyZXMgPSA1MCwgdGV4dF9zaXplID0gMywgc2hvd19jb2xuYW1lcz1GQUxTRSwKICAjICAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fc2FtcGxlcyA9IGMoIm9yZ2FuIiwgInRpbWUiLCAibWV0aG9kIiwgImRvbm9yIikpCiAgcDMgPC0gcGxvdF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIG5mZWF0dXJlcyA9IDMwLCB0ZXh0X3NpemUgPSAzKSArCiAgIHNjYWxlX3lfZGlzY3JldGUoZXhwYW5kPWMoMC4xLCAwLjEpKQogIAogIGZ1bGxfcGwgPC0gKHAxIHwgKHAyIC8gcDMpKSArCiAgICBwbG90X2xheW91dChndWlkZXM9ImNvbGxlY3QiKSAKICBnZ3NhdmUoZ2x1ZSgie2ZpZ2Rpcn0vTU9GQV97c3BsaXR9X2ZhY3RvcklEX2ZhY3RvcntmfS5wZGYiKSwgcGxvdD1mdWxsX3BsLCB3aWR0aCA9IDE1LCBoZWlnaHQgPSAxMCkKfQoKZm9yIChmIGluIDE6bW9mYV90cmFpbmVkQGRpbWVuc2lvbnMkSyl7CiAgcHJpbnQocGFzdGUwKCJTYXZpbmcgSUQgZm9yIEZhY3RvciAiLCBmLCAiLi4uIikpCiAgc2F2ZV9mYWN0b3JfaWQobW9mYV90cmFpbmVkLCBmPWYsIGZpZ2RpciA9IGZpZ2RpcikgIAp9CgojIHNhdmVfZmFjdG9yX2lkKG1vZmFfdHJhaW5lZCwgZj0xLCBmaWdkaXIgPSBmaWdkaXIpICAKIyBwbG90X3dlaWdodHMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gZiwgbmZlYXR1cmVzID0gMzAsIHRleHRfc2l6ZSA9IDMpICsKIyAgICBzY2FsZV95X2Rpc2NyZXRlKGV4cGFuZD1jKDAuMSwgMC4xKSkKYGBgCgoKCgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBnZXRfdG9wX2NlbGx0eXBlX3Blcl9mYWN0b3IgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmKXsgLS0+CjwhLS0gICByMl9kZiA8LSBnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMV1dICU+JSAtLT4KPCEtLSAgICAgZmlsdGVyKGZhY3Rvcj09cGFzdGUwKCdGYWN0b3InLGYpKSAlPiUgLS0+CjwhLS0gICAgIG11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscyA9IGxldmVscyhmYWN0b3JfZGYkZ3JvdXApKSkgLS0+CjwhLS0gICB0b3BfcXVhbnRfcjIgPC0gcXVhbnRpbGUocjJfZGYkdmFsdWUsIHByb2JzID0gc2VxKDAsIDEsIGJ5ID0gMC4yKSlbIjgwJSJdIC0tPgo8IS0tICAgdG9wX2dyb3VwcyA8LSByMl9kZiRncm91cFtyMl9kZiR2YWx1ZSA+PSB0b3BfcXVhbnRfcjJdIC0tPgo8IS0tICAgcmV0dXJuKHRvcF9ncm91cHMpIC0tPgo8IS0tIH0gLS0+Cgo8IS0tIHBsb3RfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZmFjdG9yPTIsIGdyb3Vwcz1nZXRfdG9wX2NlbGx0eXBlX3Blcl9mYWN0b3IobW9mYV90cmFpbmVkLCAyKVszOjVdLCBkb2RnZSA9IFRSVUUsIGFkZF9ib3hwbG90ID0gVFJVRSwgY29sb3JfYnk9ImRvbm9yIikgLS0+CjwhLS0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3I9MiwgZ3JvdXBzPWdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIDIpWzM6NV0sIGdyb3VwX2J5ID0gIm9yZ2FuIiwgZG9kZ2UgPSBUUlVFLCBhZGRfYm94cGxvdCA9IFRSVUUsIGNvbG9yX2J5PSJvcmdhbiIpICsgLS0+CjwhLS0gICB5bGltKDMsOCkgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBnZXRfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGdyb3Vwcz1nZXRfdG9wX2NlbGx0eXBlX3Blcl9mYWN0b3IobW9mYV90cmFpbmVkLCAyKVszOjVdLCBmYWN0b3I9MiwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpICU+JSAtLT4KPCEtLSAgIGxlZnRfam9pbihtb2ZhX3RyYWluZWRAc2FtcGxlc19tZXRhZGF0YSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyh2YWx1ZSwgZmlsbD1vcmdhbikpICsgLS0+CjwhLS0gICBnZW9tX2hpc3RvZ3JhbSgpIC0tPgo8IS0tICAgIyBnZW9tX3Ntb290aChtZXRob2Q9ImxtIikgKyAtLT4KPCEtLSAgIGdncHVicjo6c3RhdF9jb3IoKSAtLT4KPCEtLSBwbG90X2ZhY3RvcnNfdnNfY292KG1vZmFfdHJhaW5lZCwgZ3JvdXBzPWdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIDIpWzM6NV0sIGNvdmFyaWF0ZXMgPSAiIikgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSB3IDwtIGdldF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9ICdhbGwnLCBhcy5kYXRhLmZyYW1lID0gRkFMU0UpIC0tPgo8IS0tIGFzLmRhdGEuZnJhbWUodyRzY2FsZWRfbG9nY291bnRzKSAlPiUgLS0+CjwhLS0gICByb3duYW1lc190b19jb2x1bW4oImdlbmUiKSAlPiUgLS0+CjwhLS0gICB3cml0ZV9jc3YoIn4vTU9GQV93ZWlnaHRzLmNzdiIpIC0tPgo8IS0tIGBgYCAtLT4KCiMjIyMgS05OIGdyYXBoIHBlciBjZWxsdHlwZQoKYGBge3J9CiMjIEdldCBmYWN0b3JzIHRoYXQgZXhwbGFpbiBtb3N0IHZhcmlhbmNlIGluIGVhY2ggY2VsbHR5cGUKZ2V0X3RvcF9mYWN0b3JfcGVyX2NlbGx0eXBlIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZ3IsIG1pbl9SMj0yKXsKICBnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpW1sxXV0gJT4lCiAgICBmaWx0ZXIoZ3JvdXA9PWdyKSAlPiUKICAgIGZpbHRlcih2YWx1ZSA+PSBtaW5fUjIpICU+JQogICAgcHVsbChmYWN0b3IpICU+JQogICAgYXMuY2hhcmFjdGVyKCkKfQoKIyMgTWFrZSBLTk4gZ3JhcGggYmFzZWQgb24gc2ltaWxhcml0eSBvZiB0b3AgZmFjdG9ycyBmb3IgZWFjaCBjZWxsdHlwZQpnZXRfY3RfS05OX2dyYXBoIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZ3IsIG1pbl9SMj01LCBrPTUpewogICMjIEdldCBmYWN0b3JzIHRoYXQgZXhwbGFpbiBtb3N0IHZhcmlhbmNlIHBlciBjZWxsdHlwZQogIGZzIDwtIGdldF90b3BfZmFjdG9yX3Blcl9jZWxsdHlwZShtb2ZhX3RyYWluZWQsIGdyLCBtaW5fUjIgPSBtaW5fUjIpCiAgCiAgIyMgRXhjbHVkZSBmYWN0b3IxIChwcm9saWZlcmF0aW9uKQogIGZzIDwtIGZzWyFmcyAlaW4lIGMoIkZhY3RvcjEiLCAiRmFjdG9yMiIpXQogIAogICMjIE1ha2UgS05OIGdyYXBoIGZyb20gdG9wIGZhY3RvcnMKICBaIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZ3JvdXBzPWdyLCBmYWN0b3JzID0gZnMpW1sxXV0KICBrbm5fY3QgPC0gYnVpbGRLTk5HcmFwaCh0KFopLCBrPWspCiAgCiAgIyMgQWRkIGF0dHJpYnV0ZXMKICBtZXRhZGF0YV9jdCA8LSBzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZClbcm93bmFtZXMoWiksXQogICMgY292YXJpYXRlcwogIFYoa25uX2N0KSRvcmdhbiA8LSBtZXRhZGF0YV9jdCRvcmdhbgogIFYoa25uX2N0KSRhZ2UgPC0gbWV0YWRhdGFfY3QkYWdlCiAgVihrbm5fY3QpJG5fY2VsbHMgPC0gbWV0YWRhdGFfY3Qkbl9jZWxscwogIFYoa25uX2N0KSRtZXRob2QgPC0gbWV0YWRhdGFfY3QkbWV0aG9kCiAgVihrbm5fY3QpJGRvbm9yIDwtIG1ldGFkYXRhX2N0JGRvbm9yCiAgIyB0b3AgZmFjdG9ycwogIGZvciAoYyBpbiBjb2xuYW1lcyhaKSl7CiAgIHZlcnRleF9hdHRyKGtubl9jdClbW2NdXSA8LSBaWyxjXSAgCiAgfQogIAogIHJldHVybihrbm5fY3QpCiAgfQoKIyMgUGxvdCBLTk4gZ3JhcGgKcGxvdF9jdF9LTk5fZ3JhcGggPC0gZnVuY3Rpb24oa25uLCBjb2xvcl9ieT0ib3JnYW4iKXsKICAjIyBEZWZpbmUgY29sb3IgCiAgaWYgKCFjb2xvcl9ieSAlaW4lIG5hbWVzKHZlcnRleF9hdHRyKGtubikpKXsKICAgIHN0b3AoInNwZWNpZmllZCBjb2xvcl9ieSB2YXJpYWJsZSBpcyBub3QgaW4gdmVydGV4X2F0dHIoa25uKSIpCiAgfQogIAogIGlmIChjb2xvcl9ieT09Im9yZ2FuIil7IAogICAgc2NhbGVfY29sb3Jfa25uZ3JhcGggPC0gc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1vcmdfY29sb3JzKQogIH0gZWxzZSBpZiAoaXMubnVtZXJpYyh2ZXJ0ZXhfYXR0cihrbm4sIGNvbG9yX2J5KSkpewogICAgc2NhbGVfY29sb3Jfa25uZ3JhcGggPC0gc2NhbGVfY29sb3JfdmlyaWRpc19jKG9wdGlvbj0ibWFnbWEiKSAgCiAgfSBlbHNlIHsKICAgICAgc2NhbGVfY29sb3Jfa25uZ3JhcGggPC0gc2NhbGVfY29sb3JfZGlzY3JldGUoKQogICAgfQogIAogIHZlcnRleF9hdHRyKGtubiwgImNvbG9yX2J5IikgPC0gdmVydGV4X2F0dHIoa25uLCBjb2xvcl9ieSkKICAKICBnZ3JhcGgoa25uKSArCiAgICBnZW9tX2VkZ2VfbGluazAoKSArCiAgICBnZW9tX25vZGVfcG9pbnQoYWVzKGNvbG9yPWNvbG9yX2J5LCBzaXplPW5fY2VsbHMpKSArCiAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBzY2FsZV9jb2xvcl9rbm5ncmFwaCArCiAgICBzY2FsZV9zaXplKHJhbmdlPWMoMiw3KSkgCiAgfQoKZ2V0X3RvcF9mYWN0b3JfcGVyX2NlbGx0eXBlKG1vZmFfdHJhaW5lZCwgIk5LIikKCmFsbF9ncm91cHMgPC0gbmFtZXMoZ2V0X2RhdGEobW9mYV90cmFpbmVkKVtbMV1dKQprbm5fZ3JhcGhfcGwgPC0gbGFwcGx5KGFsbF9ncm91cHMsIGZ1bmN0aW9uKGcpewogIGtubiA8LSBnZXRfY3RfS05OX2dyYXBoKG1vZmFfdHJhaW5lZCwgZywgaz01LCBtaW5fUjIgPSAyKQogIHBsb3RfY3RfS05OX2dyYXBoKGtubiwgY29sb3JfYnkgPSAnb3JnYW4nKSArIGdndGl0bGUoZykKICB9KQoKa25uX2dyYXBoX3BsIDwtIHNldE5hbWVzKGtubl9ncmFwaF9wbCwgYWxsX2dyb3VwcykKa25uIDwtIGdldF9jdF9LTk5fZ3JhcGgobW9mYV90cmFpbmVkLCAnQjEnLCBrPTUsIG1pbl9SMiA9IDEpCnBsb3RfY3RfS05OX2dyYXBoKGtubiwgY29sb3JfYnkgPSAnRmFjdG9yNScpIApgYGAKCmBgYHtyfQpwbG90X2ZhY3Rvcihtb2ZhX3RyYWluZWQsZ3JvdXBzID0gJ01BVFVSRV9CJywgZmFjdG9ycyA9IGMoNSksIGdyb3VwX2J5ID0nb3JnYW4nLCBjb2xvcl9ieSA9ICJhZ2UiKQpgYGAKCgpgYGB7cn0KIyMgU2NvcmUgY29ubmVjdGl2aXR5IGJldHdlZW4gc2FtcGxlcyBmcm9tIHRoZSBzYW1lIG9yZ2FuCi5jYWxjX2Nvbm5lY3Rpdml0eV9zY29yZSA8LSBmdW5jdGlvbihrbm4sIG8pewogIGFkaiA8LSBnZXQuYWRqYWNlbmN5KGtubikKICBuX29yZyA8LSBzdW0oVihrbm4pJG9yZ2FuPT1vKQogIG5fb3RoZXIgPC0gc3VtKFYoa25uKSRvcmdhbiE9bykKICB3aXRoaW5fZWRnZXMgPC0gc3VtKGFkaltWKGtubikkb3JnYW49PW8sVihrbm4pJG9yZ2FuPT1vXSkKICBiZXR3ZWVuX2VkZ2VzIDwtIHN1bShhZGpbVihrbm4pJG9yZ2FuPT1vLFYoa25uKSRvcmdhbiE9b10pCiAgc2NvcmUgPC0gKHdpdGhpbl9lZGdlcy9iZXR3ZWVuX2VkZ2VzKSoobl9vdGhlci9uX29yZykKICByZXR1cm4oc2NvcmUpCiAgfQoKIyMgQ2FsY3VsYXRlIGNvbm5lY3Rpdml0eSBzY29yZSBmb3IgcGVybXV0YXRpb25zIG9mIG5vZGUgbGFiZWxzCmNvbm5fc2NvcmVfdGVzdCA8LSBmdW5jdGlvbihrbm4sIG8sIG5fcGVybT0xMDAwKXsKICByZWFsX3Njb3JlIDwtIC5jYWxjX2Nvbm5lY3Rpdml0eV9zY29yZShrbm4sIG8pCiAgIyMgUmFuZG9tIHBlcm11dGF0aW9ucwogIHJhbmRfc2NvcmVzIDwtIGMoKQogIGZvciAoaSBpbiAxOm5fcGVybSl7CiAgICByYW5kX2tubiA8LSBrbm4KICAgIFYocmFuZF9rbm4pJG9yZ2FuIDwtIHNhbXBsZShWKGtubikkb3JnYW4pCiAgICByYW5kX3Njb3JlcyA8LSBjKHJhbmRfc2NvcmVzLCAuY2FsY19jb25uZWN0aXZpdHlfc2NvcmUocmFuZF9rbm4sIG8pKSAgIAogIH0KICAKICBwX3ZhbCA8LSBzdW0oYyhyYW5kX3Njb3JlcywgcmVhbF9zY29yZSkgPj0gcmVhbF9zY29yZSkvKG5fcGVybSArIDEpCiAgaWYgKHBfdmFsIDwgMmUtMTYpeyBwX3ZhbCA8LSAyZS0xNn0KICByZXR1cm4oYygnc2NvcmUnPXJlYWxfc2NvcmUsJ3BfdmFsdWUnPXBfdmFsKSkKfQoKIyMgQ2FsY3VsYXRlIGNvbm5lY3Rpdml0eSBzY29yZSArIHNpZ25pZmljYW5jZSB3aXRoIHBlcm11dGF0aW9uIHRlc3QKdGVzdF9jb25uX2dyb3VwIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZywgaz01LCBtaW5fUjIgPSAyLCBuX3Blcm09MTAwMCl7CiAga25uIDwtIGdldF9jdF9LTk5fZ3JhcGgobW9mYV90cmFpbmVkLCBnLCBrPWssIG1pbl9SMiA9IG1pbl9SMikKICB0ZXN0X29yZ3MgPC0gbmFtZXModGFibGUoVihrbm4pJG9yZ2FuKSlbdGFibGUoVihrbm4pJG9yZ2FuKSA+IDJdCiAgcmV0dXJuKHNhcHBseSh0ZXN0X29yZ3MsIGZ1bmN0aW9uKG8pIGNvbm5fc2NvcmVfdGVzdChrbm4sIG8sIG5fcGVybT1uX3Blcm0pKSkKICB9Cgpjb25uZWN0aXZpdHlfdGVzdF9scyA8LSBsYXBwbHkoYWxsX2dyb3VwcywgZnVuY3Rpb24oZykgdGVzdF9jb25uX2dyb3VwKG1vZmFfdHJhaW5lZCwgZykpCmNvbm5lY3Rpdml0eV90ZXN0X2xzIDwtIHNldE5hbWVzKGNvbm5lY3Rpdml0eV90ZXN0X2xzLCBhbGxfZ3JvdXBzKQoKY29ubmVjdGl2aXR5X3Rlc3RfZGYgPC0gaW1hcChjb25uZWN0aXZpdHlfdGVzdF9scywgfiBkYXRhLmZyYW1lKHQoLngpKSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCJvcmdhbiIpICU+JSBtdXRhdGUoZ3JvdXA9LnkpKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykgJT4lCiAgbXV0YXRlKGlzX3NpZ25pZiA9IGlmZWxzZShwX3ZhbHVlIDwgMC4wMSwgVFJVRSwgRkFMU0UpKSAKCmNvbm5lY3Rpdml0eV90ZXN0X2RmICU+JQogIGdncGxvdChhZXMob3JnYW4sIGdyb3VwLGZpbGw9bG9nMTAoc2NvcmUpKSkgKwogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlPSJSZWRzIiwgZGlyZWN0aW9uID0gMSkgKwogIGdlb21fdGV4dChkYXRhPS4gJT4lIGZpbHRlcihpc19zaWduaWYpLCBsYWJlbD0iKiIsIHNpemU9NSkKCmBgYApgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpjb25uZWN0aXZpdHlfdGVzdF9kZiAlPiUKICBncm91cF9ieShncm91cCkgJT4lCiAgbXV0YXRlKG1lYW5fdmFsPW1lZGlhbihzY29yZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBhcnJhbmdlKC1tZWFuX3ZhbCkgJT4lCiAgbXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzPXVuaXF1ZShncm91cCkpKSAlPiUKICBnZ3Bsb3QoYWVzKG9yZ2FuLCBsb2cxcChzY29yZSkpKSArCiAgZ2VvbV9jb2woZmlsbD0iZ3JleSIpICsKICBnZW9tX2NvbChkYXRhPS4gJT4lIGZpbHRlcihpc19zaWduaWYpLCBhZXMoZmlsbD1vcmdhbikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9b3JnX2NvbG9ycykgICsKICBjb29yZF9mbGlwKCkgKwogIGZhY2V0X2dyaWQoZ3JvdXB+LikgKwogIHRoZW1lKHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZT0wKSkKYGBgCgojIyMjIEV4cHJlc3Npb24gb2YgdG9wIFIyIGZhY3RvcnMKCmBgYHtyfQpnZXRfdG9wX3dlaWdodF9nZW5lcyA8LSBmdW5jdGlvbihtb2ZhX3RyYWluZWQsIGYsIG5fdG9wPTIwLCB3aGljaD0idG9wIil7CiAgd19kZiA8LSBnZXRfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSBmLCBhcy5kYXRhLmZyYW1lID0gVFJVRSkgJT4lCiAgICBhcnJhbmdlKHZhbHVlKSAKICBpZiAod2hpY2g9PSJ0b3AiKSB7CiAgICB3X2RmICU+JQogICAgICB0b3BfbihuX3RvcCwgdmFsdWUpICU+JQogICAgICBwdWxsKGZlYXR1cmUpICU+JQogICAgICBhcy5jaGFyYWN0ZXIoKQogIH0gZWxzZSBpZiAod2hpY2g9PSJib3R0b20iKXsKICAgIHdfZGYgJT4lCiAgICAgIHRvcF9uKG5fdG9wLCAtdmFsdWUpICU+JQogICAgICBwdWxsKGZlYXR1cmUpICU+JQogICAgICBhcy5jaGFyYWN0ZXIoKQogICAgfQp9CgpwbG90X2RhdGFfdG9wX3dlaWdodHMgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBjdCwgZiwgbl90b3A9MjAsIHdoaWNoPSJ0b3AiKXsKICBnZW5lcyA8LSBnZXRfdG9wX3dlaWdodF9nZW5lcyhtb2ZhX3RyYWluZWQsIGYsIHdoaWNoPXdoaWNoLCBuX3RvcD1uX3RvcCkKICBkYXRhIDwtIGdldF9kYXRhKG1vZmFfdHJhaW5lZCwgZ3JvdXBzPWN0KVtbMV1dW1sxXV1bZ2VuZXMsXQogIAogIHBsX2RmIDwtIHJlc2hhcGUyOjptZWx0KGRhdGEsIHZhcm5hbWVzPWMoImdlbmUiLCAic2FtcGxlIikpICU+JQogICAgbGVmdF9qb2luKHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSkgJT4lCiAgICBhcnJhbmdlKGFnZSkgJT4lCiAgICBtdXRhdGUoc2FtcGxlPWZhY3RvcihzYW1wbGUsIGxldmVscz11bmlxdWUoc2FtcGxlKSkpICU+JQogICAgZ3JvdXBfYnkoZ2VuZSkgJT4lCiAgICBtdXRhdGUodmFsdWU9c2NhbGUodmFsdWUpKQogIHBsX2RmICU+JQogICAgZ2dwbG90KGFlcyhzYW1wbGUsIGdlbmUsIGZpbGw9dmFsdWUpKSArCiAgICBnZW9tX3RpbGUoKSArCiAgICBmYWNldF9ncmlkKC5+b3JnYW4sIHNwYWNlPSJmcmVlIiwgc2NhbGVzPSJmcmVlIikgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudDIoaGlnaD0icmVkIiwgbG93PSJibHVlIiwgbmFtZT0iU2NhbGVkXG5leHByZXNzaW9uIikgKwogICAgeGxhYigiLS0tLWFnZS0tLT4iKSArIHlsYWIoZ2x1ZSgie3doaWNofSB3ZWlnaHQgZ2VuZXMiKSkgKwogICAgdGhlbWVfYncoYmFzZV9zaXplPTE2KSArCiAgICB0aGVtZShheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBnZ3RpdGxlKGdsdWUoJ3tjdH0gLSB7Zn0nKSkKfQoKZm9yIChnIGluIGFsbF9ncm91cHMpewogIGZzIDwtIGdldF90b3BfZmFjdG9yX3Blcl9jZWxsdHlwZShtb2ZhX3RyYWluZWQsIGcsIG1pbl9SMj0zKQogIHRvcF9wbG90cyA8LSBsYXBwbHkoZnMsIGZ1bmN0aW9uKHgpIChwbG90X2RhdGFfdG9wX3dlaWdodHMobW9mYV90cmFpbmVkLCBnLCB4LCB3aGljaD0idG9wIikgKyByZW1vdmVfeF9heGlzKCkpIC8gIAogICAgICAgICAgICAgICAgICAgICAgICBwbG90X2RhdGFfdG9wX3dlaWdodHMobW9mYV90cmFpbmVkLCBnLCB4LCB3aGljaD0iYm90dG9tIikgKyBnZ3RpdGxlKCIiKQogICkKICBmdWxsX3BsIDwtd3JhcF9wbG90cyh0b3BfcGxvdHMsIG5jb2w9MSkgCiAgZ2dzYXZlKGdsdWUoIntmaWdkaXJ9L3RvcF9mYWN0b3JzX2V4cHJfe2d9LnBkZiIpLHBsb3Q9ZnVsbF9wbCwgIHdpZHRoPTEyLCBoZWlnaHQgPSA3Kmxlbmd0aCh0b3BfcGxvdHMpKQp9CgpgYGAKYGBge3IsIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD04fQpwbG90X2RhdGFfaGVhdG1hcChtb2ZhX3RyYWluZWQsIGZhY3RvciA9IDUsIGdyb3VwcyA9ICJNQVRVUkVfQiIsIHNjYWxlPSJyb3ciLCBhbm5vdGF0aW9uX3NhbXBsZXMgPSBjKCJvcmdhbiIsICJhZ2UiKSwgZmVhdHVyZXMgPSA1MCkKcGxvdF9kYXRhX2hlYXRtYXAobW9mYV90cmFpbmVkLCBmYWN0b3IgPSA1LCBncm91cHMgPSAiQjEiLCBzY2FsZT0icm93IiwgYW5ub3RhdGlvbl9zYW1wbGVzID0gYygib3JnYW4iLCAiYWdlIiksIGZlYXR1cmVzID0gNTApCmBgYAoKCiMjIyBHU0VBCmBgYHtyfQojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJNT0ZBZGF0YSIpCmxpYnJhcnkoTU9GQWRhdGEpCnV0aWxzOjpkYXRhKHJlYWN0b21lR1MpCmhlYWQocm93bmFtZXMocmVhY3RvbWVHUykpCgojIyBSZW1vdmUgcm93IHdpdGggTkEKcmVhY3RvbWVHUyA8LSByZWFjdG9tZUdTWyFpcy5uYShyb3duYW1lcyhyZWFjdG9tZUdTKSksXQpgYGAKCmBgYHtyfQpsaWJyYXJ5KEVuc0RiLkhzYXBpZW5zLnY4NikKaGcucGFpcnMgPC0gcmVhZFJEUyhzeXN0ZW0uZmlsZSgiZXhkYXRhIiwgImh1bWFuX2N5Y2xlX21hcmtlcnMucmRzIiwgcGFja2FnZT0ic2NyYW4iKSkKYWxsX2dlbmVzIDwtIGVuc2VtYmxkYjo6Z2VuZXMoRW5zRGIuSHNhcGllbnMudjg2KQpkZXRhY2gocGFja2FnZTpFbnNEYi5Ic2FwaWVucy52ODYpCmRldGFjaChwYWNrYWdlOmVuc2VtYmxkYikKCiMgZ2VuZV9uYW1lXzJfaWQgPC0gZnVuY3Rpb24oZ2VuZSl7CiMgICAgcmV0dXJuKGFsbF9nZW5lc1thbGxfZ2VuZXMkZ2VuZV9uYW1lPT1nZW5lLF0kZ2VuZV9pZFsxXSkKIyB9CiMgCiMgZ2VuZV9pZHMgPC0gc2FwcGx5KG1vZmFfdHJhaW5lZEBmZWF0dXJlc19tZXRhZGF0YSRmZWF0dXJlLCBnZW5lX25hbWVfMl9pZCkKIyByb3dEYXRhKHNjZSlbImdlbmVfaWQiXSA8LSBnZW5lX2lkcwojIHJvd0RhdGEoc2NlKVsiZ2VuZV9uYW1lIl0gPC0gcm93bmFtZXMoc2NlKQoKZ2VuZV9uYW1lc19yZWFjdG9tZSA8LSBhbGxfZ2VuZXNbY29sbmFtZXMocmVhY3RvbWVHUyldJGdlbmVfbmFtZQpjb2xuYW1lcyhyZWFjdG9tZUdTKSA8LSBnZW5lX25hbWVzX3JlYWN0b21lCmBgYAoKU3Vic2V0IHRvIGdlbmVzIHRlc3RlZApgYGB7cn0KcmVhY3RvbWVHU191bml2ZXJzZSA8LSByZWFjdG9tZUdTWywgY29sbmFtZXMocmVhY3RvbWVHUykgJWluJSBtb2ZhX3RyYWluZWRAZmVhdHVyZXNfbWV0YWRhdGEkZmVhdHVyZV0KYGBgCgoKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD03fQojIEdTRUEgb24gcG9zaXRpdmUgd2VpZ2h0cywgd2l0aCBkZWZhdWx0IG9wdGlvbnMKcmVzLnBvc2l0aXZlIDwtIHJ1bl9lbnJpY2htZW50KG1vZmFfdHJhaW5lZCwKICB2aWV3PSdzY2FsZWRfbG9nY291bnRzJywKICAjIHN0YXRpc3RpY2FsLnRlc3QgPSAnY29yLmFkai5wYXJhbWV0cmljJywKICBmZWF0dXJlLnNldHMgPSByZWFjdG9tZUdTX3VuaXZlcnNlLCAKICBzaWduID0gInBvc2l0aXZlIiwKKQoKIyBHU0VBIG9uIG5lZ2F0aXZlIHdlaWdodHMsIHdpdGggZGVmYXVsdCBvcHRpb25zCnJlcy5uZWdhdGl2ZSA8LSBydW5fZW5yaWNobWVudChtb2ZhX3RyYWluZWQsIAogIHZpZXc9J3NjYWxlZF9sb2djb3VudHMnLAogICMgc3RhdGlzdGljYWwudGVzdCA9ICdjb3IuYWRqLnBhcmFtZXRyaWMnLAogIGZlYXR1cmUuc2V0cyA9IHJlYWN0b21lR1NfdW5pdmVyc2UsIAogIHNpZ24gPSAibmVnYXRpdmUiCikKCgpmb3IgKGYgaW4gMTptb2ZhX3RyYWluZWRAZGltZW5zaW9ucyRLKXsKICBpZiAobWluKHJlcy5wb3NpdGl2ZSRwdmFsLmFkalsscGFzdGUwKCJGYWN0b3IiLCBmKV0pIDwgMC4xKSB7CiAgICBwcmludChwbG90X2VucmljaG1lbnQocmVzLnBvc2l0aXZlLCBmYWN0b3IgPSBmLCBhbHBoYT0wLjEpICsgZ2d0aXRsZSgiUG9zaXRpdmUgd2VpZ2h0cyIpICsKICAgICAgICAgICAgcGxvdF9lbnJpY2htZW50KHJlcy5uZWdhdGl2ZSwgZmFjdG9yID0gZiwgYWxwaGE9MC4xKSArIGdndGl0bGUoIk5lZ2F0aXZlIHdlaWdodHMiKSArCiAgICAgICAgICAgICAgcGxvdF9hbm5vdGF0aW9uKHRpdGxlPXBhc3RlMCgiRmFjdG9yIiwgZikpKQogICAgICB9CiAgfQpgYGAKCmBgYHtyfQpzaWduaWZfcGF0aHdheXMgPC0gcm93bmFtZXMoZGF0YS5mcmFtZShyZXMubmVnYXRpdmUkcHZhbC5hZGopKVtvcmRlcihkYXRhLmZyYW1lKHJlcy5uZWdhdGl2ZSRwdmFsLmFkailbWyJGYWN0b3I4Il1dKVswOjEwXV0KY29sbmFtZXMocmVhY3RvbWVHU191bml2ZXJzZSlbcmVhY3RvbWVHU191bml2ZXJzZVtzaWduaWZfcGF0aHdheXNbNV0sXT09MV0KcGxvdF9lbnJpY2htZW50X2RldGFpbGVkKHJlcy5uZWdhdGl2ZSwgZmFjdG9yID0gOCkKYGBgCgotLS0KCiMjIE5vdGVzCgotIEZhY3RvcjIgc2VwYXJhdGVzIEJNIGZyb20gcmVzdAotIEZhY3RvcjU6IGltbWF0dXJlIFZTIG1hdHVyZSBCIGNlbGwgcGhlbm90eXBlLCBzZXBhcmF0ZXMgbWF0dXJlIEIgY2VsbHMgYW5kIEIxIGNlbGxzIGluIGxpdmVyIGFuZCBCTSBmcm9tIHRoZSBvdGhlcnMsIG1vcmUgbWF0dXJlIHBoZW5vdHlwZSAobG93ZXIgZXhwciBvZiBWUFJFQjEgYW5kIGNvLikKCgotLS0KPCEtLSAjIyMgRmFjdG9yIGFubm90YXRpb24gIC0tPgo8IS0tIFNvIGZhciAtLT4KCjwhLS0gIyMjIyBGYWN0b3IgMSAtLT4KPCEtLSBDZWxsIGN5Y2xlIC8gcHJvbGlmZXJhdGlvbiBzaWduYXR1cmUgLS0+Cgo8IS0tICMjIyMgRmFjdG9yIDIgLS0+CjwhLS0gRXhwbGFpbnMgdmFyaWF0aW9uIGluIGxhdGUgQiBjZWxsIHN0YWdlcywgcG9zc2libHkgZGlmZmVyZW5jZSBiZXR3ZWVuIEJNIGFuZCBvdGhlciBvcmdhbnM/IC0tPgoKPCEtLSAjIyMjIEZhY3RvciAzIC0tPgo8IS0tIFRoeW11cyBzcGVjaWZpYyBUIGNlbGwgc2lnbmF0dXJlLCBlc3BlY2lhbGx5IGluIGltbWF0dXJlIFQgY2VsbHMuIEludGVyZXN0aW5nbHksIGRpZmZlcmVuY2UgYWxzbyBpbiBCMSBjZWxscywgY291bGQgYmUgc2lnbmFsbGluZyBmcm9tIHRoeW1pYyBtaWNyb2Vudmlyb25tZW50PyAtLT4KCjwhLS0gIyMjIyBGYWN0b3IgNCAtLT4KPCEtLSBWYXJpYXRpb24gd2l0aGluIHByb2dlbml0b3JzLCBhbmQgbG90cyBvZiB2YXJpYW5jZSBleHBsYWluZWQgaW4gQjEgY2VsbHMgdG9vISBTdGVtbmVzcyBtYXJrZXJzIHN1Y2ggYXMgQ0QzNCwgSE9QWC4uLiBFeHBsYWlucyBsb3RzIG9mIHZhcmlhbmNlIGluIFRyZWdzICg3LjY3JSkgLS0+Cgo8IS0tICMjIyMgRmFjdG9yIDUgLS0+CjwhLS0gSUxDIHNwZWNpZmljIC0tPgoKPCEtLSAjIyMjIEZhY3RvciA3IC0tPgo8IS0tIENvdWxkIGJlIHNpZ25hdHVyZSBvZiBzcGxlZW4gc3BlY2lmaWMgcHJvZ2VuaXRvcnMsIG9yIHNwbGVlbiBzb3VwIC0tPgoKPCEtLSAjIyMjIEZhY3RvciA4IC0tPgo8IS0tIE1vcmUgY2VsbCBjeWNsZS9wcm9saWZlcmF0aW9uLCBidXQgbG93ZXIgaW4gdGh5bXVzIHNhbXBsZXMsIFRIIHNhbXBsZXMgZXhwcmVzcyBwcm90ZWFzb21lIC0tPgoKPCEtLSAjIyMjIEZhY3RvciAxMCAtLT4KPCEtLSBtYXR1cmUgVlMgcHJvIEIgY2VsbHMgLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBnZXRfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSA3LCBhcy5kYXRhLmZyYW1lID0gVFJVRSkgJT4lIC0tPgo8IS0tICAgbGVmdF9qb2luKG1vZmFfdHJhaW5lZEBzYW1wbGVzX21ldGFkYXRhKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKHZhbHVlLCBmaWxsPW9yZ2FuKSkgKyAtLT4KPCEtLSAgIGdlb21faGlzdG9ncmFtKCkgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBsaWJyYXJ5KHBST0MpIC0tPgoKPCEtLSBnZXRfb3JnYW5fYXVjIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZiwgbywgZ3JvdXBzKXsgLS0+CjwhLS0gICAgIGRmIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFLCBncm91cHMgPSBncm91cHMpICU+JSAtLT4KPCEtLSAgICAgbGVmdF9qb2luKG1vZmFfdHJhaW5lZEBzYW1wbGVzX21ldGFkYXRhKSAtLT4KCjwhLS0gICBjYXQgPC0gYXMubnVtZXJpYyhkZiRvcmdhbj09bykgLS0+CjwhLS0gICBwcmVkIDwtIGRmJHZhbHVlIC0tPgo8IS0tICAgaWYgKHN1bShjYXQpID4gMCkgeyAtLT4KPCEtLSAgICAgcm9jX29iaiA8LSByb2MoY2F0LCBwcmVkKSAtLT4KPCEtLSAgICAgYXVjIDwtIGF1Yyhyb2Nfb2JqKSAtLT4KPCEtLSAgICAgcmV0dXJuKGFzLnZlY3RvcihhdWMpKSAtLT4KPCEtLSAgICAgfSAtLT4KPCEtLSB9IC0tPgoKPCEtLSB0b3BfZ3JfZGYgPC0gbGFwcGx5KDE6MTksIGZ1bmN0aW9uKGYpIGRhdGEuZnJhbWUodG9wX2dyb3VwPWdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIGYpLCBmYWN0b3I9ZikpICU+JSAtLT4KPCEtLSAgIHB1cnJyOjpyZWR1Y2UoYmluZF9yb3dzKSAgLS0+Cgo8IS0tIG9yZyA9ICJCTSIgLS0+CjwhLS0gQVVDX29yZyA8LSBzYXBwbHkoMTpucm93KHRvcF9ncl9kZiksIGZ1bmN0aW9uKGkpeyAtLT4KPCEtLSAgIGdldF9vcmdhbl9hdWMobW9mYV90cmFpbmVkLCAgLS0+CjwhLS0gICAgICAgICAgICAgICAgIG89b3JnLCAtLT4KPCEtLSAgICAgICAgICAgICAgICAgZj10b3BfZ3JfZGYkZmFjdG9yW2ldLCAgLS0+CjwhLS0gICAgICAgICAgICAgICAgIGdyb3VwcyA9IHRvcF9ncl9kZiR0b3BfZ3JvdXBbaV0pfSAtLT4KPCEtLSAgICkgLS0+CjwhLS0gQVVDX29yZ1tzYXBwbHkoQVVDX29yZywgaXMubnVsbCldIDwtIE5BIC0tPgo8IS0tIHRvcF9ncl9kZltbIkFVQ19vcmciXV0gPC0gdW5saXN0KEFVQ19vcmcpIC0tPgoKPCEtLSBnZ3Bsb3QodG9wX2dyX2RmLCBhZXMoZmFjdG9yLCBmaWxsPUFVQ19vcmcsIHRvcF9ncm91cCkpICArIC0tPgo8IS0tICAgZ2VvbV90aWxlKCkgKyAtLT4KPCEtLSAgIGdlb21fdGV4dChhZXMobGFiZWw9cm91bmQoQVVDX29yZywgMikpKSArIC0tPgo8IS0tICAgc2NhbGVfZmlsbF92aXJpZGlzX2MoKSAtLT4KCjwhLS0gYGBgIC0tPgoKPCEtLSAtLS0gLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBsaWJyYXJ5KEVuc0RiLkhzYXBpZW5zLnY4NikgLS0+CjwhLS0gaGcucGFpcnMgPC0gcmVhZFJEUyhzeXN0ZW0uZmlsZSgiZXhkYXRhIiwgImh1bWFuX2N5Y2xlX21hcmtlcnMucmRzIiwgcGFja2FnZT0ic2NyYW4iKSkgLS0+CjwhLS0gYWxsX2dlbmVzIDwtIGVuc2VtYmxkYjo6Z2VuZXMoRW5zRGIuSHNhcGllbnMudjg2KSAtLT4KCjwhLS0gZ2VuZV9uYW1lXzJfaWQgPC0gZnVuY3Rpb24oZ2VuZSl7IC0tPgo8IS0tICAgIHJldHVybihhbGxfZ2VuZXNbYWxsX2dlbmVzJGdlbmVfbmFtZT09Z2VuZSxdJGdlbmVfaWRbMV0pIC0tPgo8IS0tIH0gLS0+Cgo8IS0tIGdlbmVfaWRzIDwtIHNhcHBseShyb3duYW1lcyhzY2UpLCBnZW5lX25hbWVfMl9pZCkgLS0+CjwhLS0gcm93RGF0YShzY2UpWyJnZW5lX2lkIl0gPC0gZ2VuZV9pZHMgLS0+CjwhLS0gcm93RGF0YShzY2UpWyJnZW5lX25hbWUiXSA8LSByb3duYW1lcyhzY2UpIC0tPgoKPCEtLSByb3duYW1lcyhzY2UpIDwtIHJvd0RhdGEoc2NlKVtbImdlbmVfaWQiXV0gLS0+Cgo8IS0tIGFzc2lnbm1lbnRzIDwtIGN5Y2xvbmUoc2NlLCBoZy5wYWlycywgYXNzYXkudHlwZT0ibG9nY291bnRzIikgLS0+Cgo8IS0tICMjIEFkZCAicGhhc2UiIGFzc2lnbm1lbnRzIHRvIG1vZmEgLS0+CjwhLS0gc2NlJGNlbGxjeWNsZV9waGFzZSA8LSBhc3NpZ25tZW50cyRwaGFzZXMgLS0+CjwhLS0gc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpICA8LSBzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGNlbGxjeWNsZV9waGFzZT1zY2VbLG1hdGNoKHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSRzYW1wbGUsIGNvbG5hbWVzKHNjZSkpXSRjZWxsY3ljbGVfcGhhc2UpIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIHBsb3RfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSAxLCBjb2xvcl9ieSA9ICJjZWxsY3ljbGVfcGhhc2UiKSAtLT4KPCEtLSBgYGAgLS0+CgoKPCEtLSA8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9NX0gLS0+IC0tPgo8IS0tIDwhLS0gZ2V0X2ZhY3RvcnMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gMywgYXMuZGF0YS5mcmFtZSA9IFRSVUUpICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIG11dGF0ZShvcmdhbiA9IHNhcHBseShzdHJfc3BsaXQoc2FtcGxlLCAiLSIpLCBmdW5jdGlvbih4KSB4W2xlbmd0aCh4KS0zXSkpICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIGdyb3VwX2J5KGdyb3VwKSAlPiUgLS0+IC0tPgo8IS0tIDwhLS0gICBtdXRhdGUoZ3JfbWVhbiA9IG1lZGlhbih2YWx1ZSkpICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIHVuZ3JvdXAoKSAlPiUgLS0+IC0tPgo8IS0tIDwhLS0gICBhcnJhbmdlKGdyX21lYW4pICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIG11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscz11bmlxdWUoZ3JvdXApKSkgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgZ2dwbG90KGFlcyhvcmdhbiwgdmFsdWUsIGNvbG9yPW9yZ2FuKSkgKyAtLT4gLS0+CjwhLS0gPCEtLSAgIGdlb21fYm94cGxvdCgpICsgLS0+IC0tPgo8IS0tIDwhLS0gICBnZW9tX2ppdHRlcigpICsgLS0+IC0tPgo8IS0tIDwhLS0gICAjIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlPTIpICsgLS0+IC0tPgo8IS0tIDwhLS0gICBjb29yZF9mbGlwKCkgKyAtLT4gLS0+CjwhLS0gPCEtLSAgIGZhY2V0X3dyYXAoLn5ncm91cCwgc2NhbGVzID0gImZyZWVfeCIpIC0tPiAtLT4KPCEtLSA8IS0tICAgICAgICAgICAgIGdyb3VwX2J5ID0gImdyb3VwIiwgIGRvdF9zaXplID0gMC44LCBhZGRfYm94cGxvdCA9IFRSVUUsIGRvZGdlID0gVFJVRSkgKyAtLT4gLS0+CjwhLS0gPCEtLSAgIGNvb3JkX2ZsaXAoKSAtLT4gLS0+CjwhLS0gPCEtLSBgYGAgLS0+IC0tPgoKCjwhLS0gIyMgR28gYnkgY2VsbHR5cGUgaW5zdGVhZCBvZiBmYWN0b3IgLS0+Cgo8IS0tICMjIyBEQzEgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIGdldF92YXJpYW5jZV9leHBsYWluZWQobW9mYV90cmFpbmVkLCBhcy5kYXRhLmZyYW1lID0gVFJVRSlbWzFdXSAlPiUgLS0+CjwhLS0gICBmaWx0ZXIoZ3JvdXA9PSJEQzEiKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKGZhY3RvciwgdmFsdWUpKSArIGdlb21fY29sKCkgKyAtLT4KPCEtLSAgIGNvb3JkX2ZsaXAoKSArIC0tPgo8IS0tICAgZmFjZXRfd3JhcChncm91cH4uLCBuY29sID0gNiwgc2NhbGVzID0gImZyZWVfeCIpIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGMoMiw0KSwgY29sb3JfYnkgPSAib3JnYW4iLCBncm91cHMgPSAiREMxIikgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTR9IC0tPgo8IS0tIHBsb3RfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGMoNCksIGNvbG9yX2J5ID0gIm9yZ2FuIiwgZ3JvdXBfYnkgPSAib3JnYW4iLCBncm91cHMgPSAiREMxIikgLS0+CjwhLS0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gNCwgZ3JvdXBfYnkgPSAiZ3JvdXAiLCBjb2xvcl9ieSA9ICJvcmdhbiIsIGRvdF9zaXplID0gMC44LCBhZGRfYm94cGxvdCA9IFRSVUUsIGRvZGdlID0gVFJVRSkgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBwbG90X3dlaWdodHMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gNCwgbmZlYXR1cmVzID0gMzApIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdF9kYXRhX3NjYXR0ZXIobW9mYV90cmFpbmVkLCBmYWN0b3IgPSA0LCBncm91cHM9IkRDMSIsIGNvbG9yPSJvcmdhbiIsIGZlYXR1cmVzPSJITEEtRFJBIikgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSAjIyBFeHBsb3JlIGJ5IGZhY3RvciAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3IgPSAzKSAtLT4KPCEtLSBwbG90X3dlaWdodHMobW9mYV90cmFpbmVkLCBmYWN0b3IgPSAzLCBuZmVhdHVyZXMgPSAyMCkgLS0+CjwhLS0gYGBgIC0tPgoKCjwhLS0gIyMgRmluZCBmYWN0b3JzIHRoYXQgZGlzY3JpbWluYXRlIGJldHdlZW4gb3JnYW5zIC0tPgoKCjwhLS0gYGBge3J9IC0tPgo8IS0tIGdldF9vcmdhbl9BVUMgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmLCBncil7IC0tPgo8IS0tICAgZl9kZiA8LSBnZXRfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSBmLCBncm91cHMgPSBnciwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpICU+JSAtLT4KPCEtLSAgICAgIyBncm91cF9ieShncm91cCkgJT4lIC0tPgo8IS0tICAgICAjIG11dGF0ZSh2YWx1ZT1zY2FsZSh2YWx1ZSkpICU+JSAtLT4KPCEtLSAgICAgIyB1bmdyb3VwKCkgJT4lIC0tPgo8IS0tICAgICBtdXRhdGUob3JnYW4gPSBzYXBwbHkoc3RyX3NwbGl0KHNhbXBsZSwgIi0iKSwgZnVuY3Rpb24oeCkgeFtsZW5ndGgoeCktM10pKSAgLS0+CjwhLS0gICBvcmdhbnMgPC0gdW5pcXVlKGZfZGYkb3JnYW4pIC0tPgo8IS0tICAgc3VwcHJlc3NXYXJuaW5ncyhzdXBwcmVzc01lc3NhZ2VzKHtvcmdfYXVjIDwtIHNhcHBseShvcmdhbnMsIGZ1bmN0aW9uKG9yZykgcm9jKGFzLm51bWVyaWMoZl9kZiRvcmdhbj09b3JnKSwgZl9kZiR2YWx1ZSkkYXVjKX0pKSAtLT4KPCEtLSAgIGFsbF9vcmdhbnMgPC0gYXMuY2hhcmFjdGVyKHVuaXF1ZShtb2ZhX3RyYWluZWRAc2FtcGxlc19tZXRhZGF0YSRvcmdhbikpIC0tPgo8IS0tICAgb3JnX2F1YyA8LSBzZXROYW1lcyhvcmdfYXVjW2FsbF9vcmdhbnNdLCBhbGxfb3JnYW5zKSAtLT4KPCEtLSAgIHJldHVybihvcmdfYXVjKSAtLT4KPCEtLSB9IC0tPgoKPCEtLSBhbGxfb3JnYW5zIDwtIGFzLmNoYXJhY3Rlcih1bmlxdWUobW9mYV90cmFpbmVkQHNhbXBsZXNfbWV0YWRhdGEkb3JnYW4pKSAtLT4KPCEtLSBhbGxfZ3JvdXBzIDwtIGFzLmNoYXJhY3Rlcih1bmlxdWUobW9mYV90cmFpbmVkQHNhbXBsZXNfbWV0YWRhdGEkZ3JvdXApKSAtLT4KCjwhLS0gIyMgTWFzayBpZiB0b28gbGl0dGxlIHNhbXBsZXMgLS0+CjwhLS0gbl9zYW1wbGVzX21hdCA8LSBzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkob3JnYW4sIGdyb3VwKSAlPiUgLS0+CjwhLS0gICBzdW1tYXJpc2Uobl9zYW1wbGVzPW4oKSkgJT4lIC0tPgo8IS0tICAgcGl2b3Rfd2lkZXIoaWRfY29scz1jKGdyb3VwKSwgbmFtZXNfZnJvbT0ib3JnYW4iLCB2YWx1ZXNfZnJvbT0ibl9zYW1wbGVzIiwgdmFsdWVzX2ZpbGw9MCkgJT4lIC0tPgo8IS0tICAgY29sdW1uX3RvX3Jvd25hbWVzKCJncm91cCIpICU+JSAtLT4KPCEtLSAgIGFzLm1hdHJpeCgpIC0tPgoKPCEtLSBtYXNrX3BhaXJzIDwtIHQobl9zYW1wbGVzX21hdCA8IDMpIC0tPgoKPCEtLSBBVUNfbWF0IDwtIHNhcHBseShhbGxfZ3JvdXBzLCBmdW5jdGlvbihnKSBnZXRfb3JnYW5fQVVDKG1vZmFfdHJhaW5lZCwgZj0xMCwgZ3I9ZykpIC0tPgo8IS0tIEFVQ19tYXRbbWFza19wYWlyc1tyb3duYW1lcyhBVUNfbWF0KSwgY29sbmFtZXMoQVVDX21hdCldXSA8LSBOQSAtLT4KCjwhLS0gQVVDX3RocmVzaCA9IDAuOCAtLT4KPCEtLSByZXNoYXBlMjo6bWVsdChBVUNfbWF0LCB2YXJuYW1lcz1jKCJvcmdhbiIsICJncm91cCIpLCB2YWx1ZS5uYW1lPSJBVUMiKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKG9yZ2FuLCBncm91cCkpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGFlcyhzaXplPUFVQywgY29sb3I9QVVDKSkgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoQVVDID4gQVVDX3RocmVzaCksIHNoYXBlPTgsIHNpemU9Mixjb2xvcj0id2hpdGUiKSArIC0tPgo8IS0tICAgc2NhbGVfc2l6ZShsaW1pdHMgPSBjKDAuNSwxKSkgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvdXJzID0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDUsICJSZWRzIikpIC0tPgo8IS0tIGBgYCAtLT4KCgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9NH0gLS0+CjwhLS0gbGlicmFyeShwYXRjaHdvcmspIC0tPgo8IS0tIHBsb3RfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IDUsIGdyb3VwX2J5ID0gImdyb3VwIiwgY29sb3JfYnkgPSAib3JnYW4iLCBkb2RnZSA9IFRSVUUsIGFkZF9ib3hwbG90ID0gVFJVRSkgIC0tPgoKPCEtLSAgIHBsb3RfbGF5b3V0KGd1aWRlcz0iY29sbGVjdCIpIC0tPgoKPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIHBsb3Rfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSA1LCBuZmVhdHVyZXMgPSAzMCkgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBwbG90X2RhdGFfaGVhdG1hcChtb2ZhX3RyYWluZWQsIGZhY3RvciA9IDUsIHNob3dfY29sbmFtZXM9RkFMU0UpIC0tPgo8IS0tIGBgYCAtLT4KCgoKPCEtLSAjIE1vZGVsIDMgLSAgTUVGSVNUTyAgLS0+Cgo8IS0tIEFkZCB0aW1lIGFzIGNvdmFyaWF0ZSB0byBydW4gTUVGSVNUTyAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tICMjIFZlY3RvciBmb3IgdGltZSBhc3NpZ25tZW50IC0tPgo8IS0tIHRpbWVzIDwtIGRpc3RpbmN0KGRhdGEuZnJhbWUoYWdlPXNjZSRhZ2UsIG5ld19zYW1wbGUpKSAlPiUgLS0+CjwhLS0gICBjb2x1bW5fdG9fcm93bmFtZXMoJ25ld19zYW1wbGUnKSAlPiUgLS0+CjwhLS0gICAuW3NhbXBsZV9uYW1lc191bmlxdWUsXSAtLT4KCjwhLS0gc2FtcGxlc19tZXRhZGF0YShtb2ZhKVtbInRpbWUiXV0gPC0gdGltZXMgLS0+Cgo8IS0tIG1vZmEgPC0gc2V0X2NvdmFyaWF0ZXMobW9mYSwgY292YXJpYXRlcyA9ICJ0aW1lIikgLS0+CjwhLS0gbW9mYSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3IsIGZpZy5oZWlnaHQ9MTUsIGZpZy53aWR0aD0xMH0gLS0+CjwhLS0gZ2dfaW5wdXQgPC0gcGxvdF9kYXRhX292ZXJ2aWV3KG1vZmEsIC0tPgo8IS0tICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2NvdmFyaWF0ZSA9IFRSVUUsIC0tPgo8IS0tICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2RpbWVuc2lvbnMgPSBUUlVFKSAgLS0+CjwhLS0gZ2dfaW5wdXQgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSA8IS0tIEtlZXAgZ3JvdXBzIHRoYXQgc3BhbiBtdWx0aXBsZSB2aWV3cyAtLT4gLS0+CjwhLS0gPCEtLSBgYGB7cn0gLS0+IC0tPgo8IS0tIDwhLS0gZ3Jfc2FtcGxlcyA8LSBzcGxpdChzYW1wbGVzX21ldGFkYXRhKG1vZmEpJHNhbXBsZSwgc2FtcGxlc19tZXRhZGF0YShtb2ZhKSRncm91cCkgLS0+IC0tPgo8IS0tIDwhLS0gYWxsKGlzLm5hKGRhdGEkQk1bLGdyX3NhbXBsZXMkQmFzb3BoaWxdKSkgLS0+IC0tPgo8IS0tIDwhLS0gbGFwcGx5KHVuaXF1ZShzYW1wbGVzX21ldGFkYXRhKG1vZmEpW1siZ3JvdXAiXV0pLCBmdW5jdGlvbih4KSBkYXRhJEJNW10pIC0tPiAtLT4KCgo8IS0tIDwhLS0gbW9mYUBkYXRhIC0tPiAtLT4KPCEtLSA8IS0tIHN1YnNlKG1vZmEpWyxzYW1wbGVzX21ldGFkYXRhKG1vZmEpW1siZ3JvdXAiXV0gPT0gIkJhc29waGlsIl0gLS0+IC0tPgo8IS0tIDwhLS0gYGBgIC0tPiAtLT4KCjwhLS0gUHJlcGFyZSA0IHRyYWluaW5nIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gZGF0YV9vcHRzIDwtIGdldF9kZWZhdWx0X2RhdGFfb3B0aW9ucyhtb2ZhKSAtLT4KCjwhLS0gbW9kZWxfb3B0cyA8LSBnZXRfZGVmYXVsdF9tb2RlbF9vcHRpb25zKG1vZmEpIC0tPgo8IS0tIG1vZGVsX29wdHMkbnVtX2ZhY3RvcnMgPC0gMTAgLS0+Cgo8IS0tIHRyYWluX29wdHMgPC0gZ2V0X2RlZmF1bHRfdHJhaW5pbmdfb3B0aW9ucyhtb2ZhKSAtLT4KPCEtLSB0cmFpbl9vcHRzJHNlZWQgPC0gMjAyMCAtLT4KPCEtLSB0cmFpbl9vcHRzJGNvbnZlcmdlbmNlX21vZGUgPC0gImZhc3QiICMgdXNlICJmYXN0IiBmb3IgZmFzdGVyIHRyYWluaW5nIC0tPgoKPCEtLSBtZWZpc3RvX29wdHMgPC0gZ2V0X2RlZmF1bHRfbWVmaXN0b19vcHRpb25zKG1vZmEpIC0tPgo8IS0tIG1lZmlzdG9fb3B0cyR3YXJwaW5nIDwtIEZBTFNFIC0tPgo8IS0tICMgbWVmaXN0b19vcHRzJHNwYXJzZUdQIDwtIFRSVUUgLS0+Cgo8IS0tIG1vZmEgPC0gcHJlcGFyZV9tb2ZhKCAtLT4KPCEtLSAgIG9iamVjdCA9IG1vZmEsIC0tPgo8IS0tICAgZGF0YV9vcHRpb25zID0gZGF0YV9vcHRzLCAtLT4KPCEtLSAgIG1vZGVsX29wdGlvbnMgPSBtb2RlbF9vcHRzLCAtLT4KPCEtLSAgIHRyYWluaW5nX29wdGlvbnMgPSB0cmFpbl9vcHRzLCAtLT4KPCEtLSAgIG1lZmlzdG9fb3B0aW9ucyA9IG1lZmlzdG9fb3B0cyAtLT4KPCEtLSApICAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tICMjIFRyYWluIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gb3V0ZmlsZSA8LSAiL25mcy90ZWFtMjA1L2VkNi9kYXRhL0ZldGFsX2ltbXVuZS9teWVsb2lkX21lZmlzdG9fbW9kZWwuaGRmNSIgLS0+CjwhLS0gbW9mYV90cmFpbmVkIDwtIHJ1bl9tb2ZhKG1vZmEsIG91dGZpbGUgPSBvdXRmaWxlKSAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tICMjIExvYWQgdHJhaW5lZCBtb2RlbCAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gbW9mYV90cmFpbmVkIDwtIGxvYWRfbW9kZWwob3V0ZmlsZSwgbG9hZF9pbnRlcnBvbF9aID0gVFJVRSkgLS0+CjwhLS0gYGBgIC0tPgoK